A set of tweaks to simplify electron packaging (#421)

* Replace `ormconfig.js` with a newer mechanism of configuring
    TypeORM that can be included in the source code properly.
    The path to `ormconfig.js` has always been awkward to handle,
    and eliminating the file makes building different Grist setups
    a bit simpler.
  * Remove `electron` package. It is barely used, just for some old
    remnants of an older attempt at electron packaging. It was used
    for two types, which I left at `any` for now. More code pruning is
    no doubt possible here, but I'd rather do it when Electron packaging
    has solidified.
  * Add a hook for replacing the login system, and for adding some
    extra middleware the login system may need.
  * Add support for some more possible locations of Python, which
    arise when a standalone version of it is included in the Electron
    package. This isn't very general purpose, just configurations
    that I found useful.
  * Support using grist-core within a yarn workspace - the only tweak
    needed was webpack related.
  * Allow an external ID to be optionally associated with documents.
pull/382/head
Paul Fitzpatrick 1 year ago committed by GitHub
parent d55e56297e
commit f7f76fb5e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -103,7 +103,6 @@ COPY --from=sandbox /runsc /usr/bin/runsc
# Add files needed for running server.
ADD package.json /grist/package.json
ADD ormconfig.js /grist/ormconfig.js
ADD bower_components /grist/bower_components
ADD sandbox /grist/sandbox
ADD plugins /grist/plugins

@ -40,7 +40,6 @@ import { getOriginUrl } from 'app/common/urlUtils';
import { GristAPI, RPC_GRISTAPI_INTERFACE } from 'app/plugin/GristAPI';
import { RenderOptions, RenderTarget } from 'app/plugin/RenderOptions';
import { checkers } from 'app/plugin/TypeCheckers';
import { IpcMessageEvent } from 'electron';
import { IMsgCustom, IMsgRpcCall, Rpc } from 'grain-rpc';
import { Disposable } from './dispose';
const G = getBrowserGlobals('document', 'window');
@ -316,7 +315,7 @@ class WebviewProcess extends ViewProcess {
// TODO: find a way for keyboard events to play nice when webviews are non-modal.
Mousetrap.setPaused(true);
this.autoDisposeCallback(() => Mousetrap.setPaused(false));
webview.addEventListener('ipc-message', (event: IpcMessageEvent) => {
webview.addEventListener('ipc-message', (event: any /* IpcMessageEvent */) => {
// The event object passed to the listener is missing proper documentation. In the examples
// listed in https://electronjs.org/docs/api/ipc-main the arguments should be passed to the
// listener after the event object, but this is not happening here. Only we know it is a

@ -14,7 +14,6 @@ import {GristLoadConfig} from 'app/common/gristUrls';
import {byteString, safeJsonParse} from 'app/common/gutil';
import {FetchUrlOptions, UPLOAD_URL_PATH, UploadResult} from 'app/common/uploads';
import {docUrl} from 'app/common/urlUtils';
import {OpenDialogOptions} from 'electron';
import noop = require('lodash/noop');
import trimStart = require('lodash/trimStart');
import {basename} from 'path'; // made available by webpack using path-browserify module.
@ -69,18 +68,18 @@ function getFileDialogOptions(options: SelectFileOptions): FileDialogOptions {
}
// Helper to convert SelectFileOptions to electron's OpenDialogOptions.
function getElectronOptions(options: SelectFileOptions): OpenDialogOptions {
const resOptions: OpenDialogOptions = {
filters: [],
function getElectronOptions(options: SelectFileOptions) /*: OpenDialogOptions */ {
const resOptions /*: OpenDialogOptions*/ = {
filters: [] as Array<{name: string, extensions: any}>,
properties: ['openFile'],
};
if (options.extensions) {
// Electron does not expect leading period.
const extensions = options.extensions.map(e => trimStart(e, '.'));
resOptions.filters!.push({name: 'Select files', extensions});
resOptions.filters.push({name: 'Select files', extensions});
}
if (options.multiple) {
resOptions.properties!.push('multiSelections');
resOptions.properties.push('multiSelections');
}
return resOptions;
}

@ -115,6 +115,8 @@ export interface DocumentOptions {
description?: string|null;
icon?: string|null;
openMode?: OpenDocMode|null;
externalId?: string|null; // A slot for storing an externally maintained id.
// Not used in grist-core, but handy for Electron app.
}
export interface DocumentProperties extends CommonProperties {

@ -99,6 +99,9 @@ export class Document extends Resource {
if (props.options.icon !== undefined) {
this.options.icon = sanitizeIcon(props.options.icon);
}
if (props.options.externalId !== undefined) {
this.options.externalId = props.options.externalId;
}
// Normalize so that null equates with absence.
for (const key of Object.keys(this.options) as Array<keyof DocumentOptions>) {
if (this.options[key] === null) {

@ -36,7 +36,8 @@ import {DocWorkerInfo, IDocWorkerMap} from 'app/server/lib/DocWorkerMap';
import {expressWrap, jsonErrorHandler, secureJsonErrorHandler} from 'app/server/lib/expressWrap';
import {Hosts, RequestWithOrg} from 'app/server/lib/extractOrg';
import {addGoogleAuthEndpoint} from "app/server/lib/GoogleAuth";
import {DocTemplate, GristLoginMiddleware, GristServer, RequestWithGrist} from 'app/server/lib/GristServer';
import {DocTemplate, GristLoginMiddleware, GristLoginSystem, GristServer,
RequestWithGrist} from 'app/server/lib/GristServer';
import {initGristSessions, SessionStore} from 'app/server/lib/gristSessions';
import {HostedStorageManager} from 'app/server/lib/HostedStorageManager';
import {IBilling} from 'app/server/lib/IBilling';
@ -157,6 +158,7 @@ export class FlexServer implements GristServer {
private _getSignUpRedirectUrl: (req: express.Request, target: URL) => Promise<string>;
private _getLogoutRedirectUrl: (req: express.Request, nextUrl: URL) => Promise<string>;
private _sendAppPage: (req: express.Request, resp: express.Response, options: ISendAppPageOptions) => Promise<void>;
private _getLoginSystem?: () => Promise<GristLoginSystem>;
constructor(public port: number, public name: string = 'flexServer',
public readonly options: FlexServerOptions = {}) {
@ -233,6 +235,11 @@ export class FlexServer implements GristServer {
});
}
// Allow overridding the login system.
public setLoginSystem(loginSystem: () => Promise<GristLoginSystem>) {
this._getLoginSystem = loginSystem;
}
public getHost(): string {
return `${this.host}:${this.getOwnPort()}`;
}
@ -481,12 +488,19 @@ export class FlexServer implements GristServer {
this.app.use(/^\/help\//, expressWrap(async (req, res) => {
res.redirect('https://support.getgrist.com');
}));
// If there is a directory called "static_ext", serve material from there
// as well. This isn't used in grist-core but is handy for extensions such
// as an Electron app.
const staticExtDir = getAppPathTo(this.appRoot, 'static') + '_ext';
const staticExtApp = fse.existsSync(staticExtDir) ?
express.static(staticExtDir, options) : null;
const staticApp = express.static(getAppPathTo(this.appRoot, 'static'), options);
const bowerApp = express.static(getAppPathTo(this.appRoot, 'bower_components'), options);
if (process.env.GRIST_LOCALES_DIR) {
const locales = express.static(process.env.GRIST_LOCALES_DIR, options);
this.app.use("/locales", this.tagChecker.withTag(locales));
}
if (staticExtApp) { this.app.use(this.tagChecker.withTag(staticExtApp)); }
this.app.use(this.tagChecker.withTag(staticApp));
this.app.use(this.tagChecker.withTag(bowerApp));
}
@ -700,7 +714,7 @@ export class FlexServer implements GristServer {
this.addOrg();
// Create the sessionStore and related objects.
const {sessions, sessionMiddleware, sessionStore} = initGristSessions(this.instanceRoot, this);
const {sessions, sessionMiddleware, sessionStore} = initGristSessions(getUnpackedAppRoot(this.instanceRoot), this);
this.app.use(sessionMiddleware);
this.app.use(signInStatusMiddleware);
@ -901,11 +915,16 @@ export class FlexServer implements GristServer {
// TODO: We could include a third mock provider of login/logout URLs for better tests. Or we
// could create a mock SAML identity provider for testing this using the SAML flow.
const loginSystem = await (process.env.GRIST_TEST_LOGIN ? getTestLoginSystem() : getLoginSystem());
const loginSystem = await (process.env.GRIST_TEST_LOGIN ? getTestLoginSystem() :
(this._getLoginSystem?.() || getLoginSystem()));
this._loginMiddleware = await loginSystem.getMiddleware(this);
this._getLoginRedirectUrl = tbind(this._loginMiddleware.getLoginRedirectUrl, this._loginMiddleware);
this._getSignUpRedirectUrl = tbind(this._loginMiddleware.getSignUpRedirectUrl, this._loginMiddleware);
this._getLogoutRedirectUrl = tbind(this._loginMiddleware.getLogoutRedirectUrl, this._loginMiddleware);
const wildcardMiddleware = this._loginMiddleware.getWildcardMiddleware?.();
if (wildcardMiddleware?.length) {
this.app.use(wildcardMiddleware);
}
}
public addComm() {

@ -62,6 +62,8 @@ export interface GristLoginMiddleware {
getLoginOrSignUpMiddleware?(): express.RequestHandler[];
// Optional middleware for the GET /logout route.
getLogoutMiddleware?(): express.RequestHandler[];
// Optional middleware for all routes.
getWildcardMiddleware?(): express.RequestHandler[];
// Returns arbitrary string for log.
addEndpoints(app: express.Express): Promise<string>;
// Optionally, extract profile from request. Result can be a profile,

@ -40,7 +40,7 @@ export async function getMinimalLoginSystem(): Promise<GristLoginSystem> {
};
}
function getDefaultProfile(): UserProfile {
export function getDefaultProfile(): UserProfile {
return {
email: process.env.GRIST_DEFAULT_EMAIL || 'you@example.com',
name: 'You',

@ -5,6 +5,7 @@ import {arrayToString} from 'app/common/arrayToString';
import * as marshal from 'app/common/marshal';
import {ISandbox, ISandboxCreationOptions, ISandboxCreator} from 'app/server/lib/ISandbox';
import log from 'app/server/lib/log';
import {getAppRoot, getAppRootFor, getUnpackedAppRoot} from 'app/server/lib/places';
import {
DirectProcessControl,
ISandboxControl,
@ -575,7 +576,7 @@ function gvisor(options: ISandboxOptions): SandboxProcess {
// Check for local virtual environments created with core's
// install:python2 or install:python3 targets. They'll need
// some extra sharing to make available in the sandbox.
const venv = path.join(process.cwd(),
const venv = path.join(getAppRootFor(getAppRoot(), 'sandbox'),
pythonVersion === '2' ? 'venv' : 'sandbox_venv3');
if (fs.existsSync(venv)) {
wrapperArgs.addMount(venv);
@ -869,19 +870,24 @@ function findPython(command: string|undefined, preferredVersion?: string) {
// TODO: rationalize this, it is a product of haphazard growth.
const prefs = preferredVersion === '2' ? ['venv', 'sandbox_venv3'] : ['sandbox_venv3', 'venv'];
for (const venv of prefs) {
const pythonPath = path.join(process.cwd(), venv, 'bin', 'python');
if (fs.existsSync(pythonPath)) {
command = pythonPath;
break;
const base = getUnpackedAppRoot();
// Try a battery of possible python executable paths when python is installed
// in a standalone directory.
// This battery of possibilities comes from Electron packaging, where python
// is bundled with Grist. Not all the possibilities are needed (there are
// multiple popular python bundles per OS).
for (const possiblePath of [['bin', 'python'], ['bin', 'python3'],
['Scripts', 'python.exe'], ['python.exe']] as const) {
const pythonPath = path.join(base, venv, ...possiblePath);
if (fs.existsSync(pythonPath)) {
return pythonPath;
}
}
}
// Fall back on system python.
if (!command) {
command = which.sync(preferredVersion === '2' ? 'python2' : 'python3', {nothrow: true})
|| which.sync(preferredVersion === '2' ? 'python2.7' : 'python3.9', {nothrow: true})
|| which.sync('python');
}
return command;
return which.sync(preferredVersion === '2' ? 'python2' : 'python3', {nothrow: true})
|| which.sync(preferredVersion === '2' ? 'python2.7' : 'python3.9', {nothrow: true})
|| which.sync('python');
}
/**

@ -1,6 +1,7 @@
import {synchronizeProducts} from 'app/gen-server/entity/Product';
import {codeRoot} from 'app/server/lib/places';
import {Mutex} from 'async-mutex';
import {Connection, createConnection, getConnection} from 'typeorm';
import {Connection, createConnection, DataSourceOptions, getConnection} from 'typeorm';
// Summary of migrations found in database and in code.
interface MigrationSummary {
@ -61,7 +62,7 @@ export async function getOrCreateConnection(): Promise<Connection> {
if (!String(e).match(/ConnectionNotFoundError/)) {
throw e;
}
const connection = await createConnection();
const connection = await createConnection(getTypeORMSettings());
// When using Sqlite, set a busy timeout of 3s to tolerate a little
// interference from connections made by tests. Logging doesn't show
// any particularly slow queries, but bad luck is possible.
@ -98,3 +99,49 @@ export async function undoLastMigration(connection: Connection) {
});
if (sqlite) { await connection.query("PRAGMA foreign_keys = ON;"); }
}
// Replace the old janky ormconfig.js file, which was always a source of
// pain to use since it wasn't properly integrated into the typescript
// project.
function getTypeORMSettings(): DataSourceOptions {
// If we have a redis server available, tell typeorm. Then any queries built with
// .cache() called on them will be cached via redis.
// We use a separate environment variable for the moment so that we don't have to
// enable this until we really need it.
const redisUrl = process.env.TYPEORM_REDIS_URL ? new URL(process.env.TYPEORM_REDIS_URL) : undefined;
const cache = redisUrl ? {
cache: {
type: "redis",
options: {
host: redisUrl.hostname,
port: parseInt(redisUrl.port || "6379", 10)
}
} as const
} : undefined;
return {
"name": process.env.TYPEORM_NAME || "default",
"type": (process.env.TYPEORM_TYPE as any) || "sqlite", // officially, TYPEORM_CONNECTION -
// but if we use that, this file will never
// be read, and we can't configure
// caching otherwise.
"database": process.env.TYPEORM_DATABASE || "landing.db",
"username": process.env.TYPEORM_USERNAME || undefined,
"password": process.env.TYPEORM_PASSWORD || undefined,
"host": process.env.TYPEORM_HOST || undefined,
"port": process.env.TYPEORM_PORT ? parseInt(process.env.TYPEORM_PORT, 10) : undefined,
"synchronize": false,
"migrationsRun": false,
"logging": process.env.TYPEORM_LOGGING === "true",
"entities": [
`${codeRoot}/app/gen-server/entity/*.js`
],
"migrations": [
`${codeRoot}/app/gen-server/migration/*.js` // migration files don't actually get packaged.
],
"subscribers": [
`${codeRoot}/app/gen-server/subscriber/*.js`
],
...cache,
};
}

@ -9,14 +9,25 @@ import * as path from 'path';
*/
export const codeRoot = path.dirname(path.dirname(path.dirname(__dirname)));
let _cachedAppRoot: string|undefined;
/**
* Returns the appRoot, i.e. the directory containing ./sandbox, ./node_modules, ./ormconfig.js,
* Returns the appRoot, i.e. the directory containing ./sandbox, ./node_modules,
* etc.
*/
export function getAppRoot(): string {
if (_cachedAppRoot) { return _cachedAppRoot; }
_cachedAppRoot = getAppRootWithoutCaching();
return _cachedAppRoot;
}
// Uncached version of getAppRoot()
function getAppRootWithoutCaching(): string {
if (process.env.APP_ROOT_PATH) { return process.env.APP_ROOT_PATH; }
if (codeRoot.endsWith('/_build/core')) { return path.dirname(path.dirname(codeRoot)); }
return codeRoot.endsWith('/_build') ? path.dirname(codeRoot) : codeRoot;
if (codeRoot.endsWith('/_build/core') || codeRoot.endsWith('\\_build\\core')) {
return path.dirname(path.dirname(codeRoot));
}
return (codeRoot.endsWith('/_build') || codeRoot.endsWith('\\_build')) ? path.dirname(codeRoot) : codeRoot;
}
/**
@ -25,7 +36,14 @@ export function getAppRoot(): string {
* which is that .asar file in packaged form, and returns a directory where
* remaining files are available on the regular filesystem.
*/
export function getUnpackedAppRoot(appRoot: string): string {
export function getUnpackedAppRoot(appRoot: string = getAppRoot()): string {
if (path.basename(appRoot) == 'app.asar') {
return path.resolve(path.dirname(appRoot), 'app.asar.unpacked');
}
if (path.dirname(appRoot).endsWith('app.asar')) {
return path.resolve(path.dirname(path.dirname(appRoot)),
'app.asar.unpacked', 'core');
}
return path.resolve(path.dirname(appRoot), path.basename(appRoot, '.asar'));
}

@ -6,6 +6,7 @@
*/
import {FlexServer, FlexServerOptions} from 'app/server/lib/FlexServer';
import {GristLoginSystem} from 'app/server/lib/GristServer';
import log from 'app/server/lib/log';
// Allowed server types. We'll start one or a combination based on the value of GRIST_SERVERS
@ -36,13 +37,14 @@ interface ServerOptions extends FlexServerOptions {
// logToConsole is set to true)
externalStorage?: boolean; // If set, documents saved to external storage such as s3 (default is to check environment
// variables, which get set in various ways in dev/test entry points)
loginSystem?: () => Promise<GristLoginSystem>;
}
/**
* Start a server on the given port, including the functionality specified in serverTypes.
*/
export async function main(port: number, serverTypes: ServerType[],
options: ServerOptions = {logToConsole: true}) {
options: ServerOptions = {}) {
const includeHome = serverTypes.includes("home");
const includeDocs = serverTypes.includes("docs");
const includeStatic = serverTypes.includes("static");
@ -50,6 +52,10 @@ export async function main(port: number, serverTypes: ServerType[],
const server = new FlexServer(port, `server(${serverTypes.join(",")})`, options);
if (options.loginSystem) {
server.setLoginSystem(options.loginSystem);
}
server.addCleanup();
server.setDirectory();
@ -58,7 +64,7 @@ export async function main(port: number, serverTypes: ServerType[],
server.testAddRouter();
}
if (options.logToConsole) { server.addLogging(); }
if (options.logToConsole !== false) { server.addLogging(); }
if (options.externalStorage === false) { server.disableExternalStorage(); }
await server.loadConfig();

@ -2,10 +2,25 @@
set -e
# Use a built-in standalone version of Python if available in a directory
# called python. This is used for Electron packaging. The standalone Python
# will have extra packages installed, and then be moved to a standard location
# (sandbox_venv3).
for possible_path in python/bin/python python/bin/python3 \
python/Scripts/python.exe python/python.exe; do
if [[ -e $possible_path ]]; then
echo "found $possible_path"
buildtools/prepare_python3.sh $possible_path python
# Make sure Python2 sandbox is not around.
rm -rf venv
exit 0
fi
done
echo "Use Python3 if available and recent enough, otherwise Python2"
if python3 -c 'import sys; assert sys.version_info >= (3,9)' 2> /dev/null; then
# Default to python3 if recent enough.
buildtools/prepare_python3.sh
buildtools/prepare_python3.sh python3
# Make sure python2 isn't around.
rm -rf venv
else

@ -1,12 +1,32 @@
#!/usr/bin/env bash
# Prepare a Python3 sandbox in the sandbox_venv3 directory.
# Optionally, can be called with the command to use for Python,
# and the directory of a standalone version of Python to incorporate.
set -e
echo "Making Python3 sandbox"
if [ ! -e sandbox_venv3 ]; then
python3 -m venv sandbox_venv3
if [[ -e sandbox_venv3 ]]; then
echo "Have Python3 sandbox"
exit 0
fi
python="$1"
python_dir="$2"
if [[ "$python_dir" = "" ]]; then
python=python3
pip=sandbox_venv3/bin/pip
echo "Making Python3 sandbox"
$python -m venv sandbox_venv3
else
pip="$python -m pip"
fi
echo "Updating Python3 packages"
sandbox_venv3/bin/pip install --no-deps -r sandbox/requirements3.txt
$pip install --no-deps -r sandbox/requirements3.txt
if [[ ! -e sandbox_venv3 ]]; then
echo "Moving $python_dir to sandbox_venv3"
mv $python_dir sandbox_venv3
fi
echo "Python3 packages ready in sandbox_venv3"

@ -1,5 +1,10 @@
const path = require('path');
// Get path to top-level node_modules if in a yarn workspace.
// Otherwise node_modules one level up won't get resolved.
// This is used in Electron packaging.
const base = path.dirname(path.dirname(require.resolve('grainjs/package.json')));
module.exports = {
target: 'web',
entry: {
@ -18,7 +23,8 @@ module.exports = {
path.resolve('.'),
path.resolve('./ext'),
path.resolve('./stubs'),
path.resolve('./node_modules')
path.resolve('./node_modules'),
base,
],
fallback: {
'path': require.resolve("path-browserify"),

@ -2,6 +2,11 @@ const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const { ProvidePlugin } = require('webpack');
const path = require('path');
// Get path to top-level node_modules if in a yarn workspace.
// Otherwise node_modules one level up won't get resolved.
// This is used in Electron packaging.
const base = path.dirname(path.dirname(require.resolve('grainjs/package.json')));
module.exports = {
target: 'web',
entry: {
@ -39,7 +44,8 @@ module.exports = {
path.resolve('.'),
path.resolve('./ext'),
path.resolve('./stubs'),
path.resolve('./node_modules')
path.resolve('./node_modules'),
base,
],
fallback: {
'path': require.resolve("path-browserify"),

@ -1,49 +0,0 @@
// Cache configuration for typeorm does not seem available via ormconfig.env, so
// we use ormconfig.js style.
const {codeRoot} = require('app/server/lib/places');
module.exports = {
"name": process.env.TYPEORM_NAME || "default",
"type": process.env.TYPEORM_TYPE || "sqlite", // officially, TYPEORM_CONNECTION -
// but if we use that, this file will never
// be read, and we can't configure
// caching otherwise.
"database": process.env.TYPEORM_DATABASE || "landing.db",
"username": process.env.TYPEORM_USERNAME || null,
"password": process.env.TYPEORM_PASSWORD || null,
"host": process.env.TYPEORM_HOST || null,
"port": process.env.TYPEORM_PORT || null,
"synchronize": false,
"migrationsRun": false,
"logging": process.env.TYPEORM_LOGGING === "true",
"entities": [
`${codeRoot}/app/gen-server/entity/*.js`
],
"migrations": [
`${codeRoot}/app/gen-server/migration/*.js` // migration files don't actually get packaged.
],
"subscribers": [
`${codeRoot}/app/gen-server/subscriber/*.js`
],
"cli": {
"entitiesDir": `${codeRoot}/app/gen-server/entity`,
"migrationsDir": `${codeRoot}/app/gen-server/migration`,
"subscribersDir": `${codeRoot}/app/gen-server/subscriber`
}
};
// If we have a redis server available, tell typeorm. Then any queries built with
// .cache() called on them will be cached via redis.
// We use a separate environment variable for the moment so that we don't have to
// enable this until we really need it.
if (process.env.TYPEORM_REDIS_URL) {
const url = require('url').parse(process.env.TYPEORM_REDIS_URL);
module.exports.cache = {
type: "redis",
options: {
host: url.hostname,
port: parseInt(url.port || "6379", 10)
}
};
}

@ -131,7 +131,6 @@
"csv": "4.0.0",
"diff-match-patch": "1.0.5",
"double-ended-queue": "2.1.0-0",
"electron": "19.0.9",
"exceljs": "4.2.1",
"express": "4.16.4",
"file-type": "16.5.4",

@ -218,22 +218,6 @@
resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@electron/get@^1.14.1":
version "1.14.1"
resolved "https://registry.npmjs.org/@electron/get/-/get-1.14.1.tgz"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
fs-extra "^8.1.0"
got "^9.6.0"
progress "^2.0.3"
semver "^6.2.0"
sumchecker "^3.0.1"
optionalDependencies:
global-agent "^3.0.0"
global-tunnel-ng "^2.7.1"
"@eslint/eslintrc@^1.3.0":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
@ -831,11 +815,6 @@
resolved "https://registry.npmjs.org/@types/node/-/node-14.17.6.tgz"
integrity sha512-iBxsxU7eswQDGhlr3AiamBxOssaYxbM+NKXVil8jg9yFXvrfEFbDumLD/2dMTB+zYyg7w+Xjt8yuxfdbUHAtcQ==
"@types/node@^16.11.26":
version "16.11.58"
resolved "https://registry.npmjs.org/@types/node/-/node-16.11.58.tgz"
integrity sha512-uMVxJ111wpHzkx/vshZFb6Qni3BOMnlWLq7q9jrwej7Yw/KvjsEbpxCCxw+hLKxexFMc8YmpG8J9tnEe/rKsIg==
"@types/parse5@*":
version "7.0.0"
resolved "https://registry.npmjs.org/@types/parse5/-/parse5-7.0.0.tgz"
@ -1780,11 +1759,6 @@ body-parser@1.18.3:
raw-body "2.3.3"
type-is "~1.6.16"
boolean@^3.0.1:
version "3.2.0"
resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz"
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
bootstrap-datepicker@1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/bootstrap-datepicker/-/bootstrap-datepicker-1.9.0.tgz"
@ -2539,14 +2513,6 @@ concat-stream@~1.5.0, concat-stream@~1.5.1:
readable-stream "~2.0.0"
typedarray "~0.0.5"
config-chain@^1.1.11:
version "1.1.13"
resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
dependencies:
ini "^1.3.4"
proto-list "~1.2.1"
configstore@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz"
@ -2979,11 +2945,6 @@ detect-libc@^2.0.0:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
detective@^4.0.0:
version "4.7.1"
resolved "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz"
@ -3126,15 +3087,6 @@ electron-to-chromium@^1.4.251:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
electron@19.0.9:
version "19.0.9"
resolved "https://registry.npmjs.org/electron/-/electron-19.0.9.tgz"
integrity sha512-ooEwrv8Y7NSzdhKcl6kPCYecnzcg5nFWuS5ryG+VFH3MMBR8zXh9nW2wLsZrBz6OGUxXrcc5BKBC7dA8C6RhGQ==
dependencies:
"@electron/get" "^1.14.1"
"@types/node" "^16.11.26"
extract-zip "^1.0.3"
elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.4"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz"
@ -3168,7 +3120,7 @@ encode-utf8@^1.0.3:
resolved "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
encodeurl@^1.0.2, encodeurl@~1.0.2:
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
@ -3207,7 +3159,7 @@ entities@^4.3.0:
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
envinfo@^7.7.3:
@ -3664,7 +3616,7 @@ extend@^3.0.0, extend@^3.0.2, extend@~3.0.2:
resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extract-zip@^1.0.3, extract-zip@^1.6.7:
extract-zip@^1.6.7:
version "1.7.0"
resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz"
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
@ -3921,7 +3873,7 @@ fs-extra@^4.0.2, fs-extra@^4.0.3:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.0.1, fs-extra@^8.1.0:
fs-extra@^8.0.1:
version "8.1.0"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
@ -4192,18 +4144,6 @@ glob@~3.2.7:
inherits "2"
minimatch "0.3"
global-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz"
integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
dependencies:
boolean "^3.0.1"
es6-error "^4.1.1"
matcher "^3.0.0"
roarr "^2.15.3"
semver "^7.3.2"
serialize-error "^7.0.1"
global-dirs@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz"
@ -4211,16 +4151,6 @@ global-dirs@^2.0.1:
dependencies:
ini "^1.3.5"
global-tunnel-ng@^2.7.1:
version "2.7.1"
resolved "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz"
integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
dependencies:
encodeurl "^1.0.2"
lodash "^4.17.10"
npm-conf "^1.1.3"
tunnel "^0.0.6"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@ -4233,13 +4163,6 @@ globals@^13.15.0, globals@^13.19.0:
dependencies:
type-fest "^0.20.2"
globalthis@^1.0.1:
version "1.0.3"
resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz"
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
dependencies:
define-properties "^1.1.3"
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@ -4793,11 +4716,6 @@ inherits@2.0.3:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.4:
version "1.3.8"
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
ini@^1.3.5, ini@~1.3.0:
version "1.3.7"
resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz"
@ -5331,7 +5249,7 @@ json-stream@^1.0.0:
resolved "https://registry.yarnpkg.com/json-stream/-/json-stream-1.0.0.tgz#1a3854e28d2bbeeab31cc7ddf683d2ddc5652708"
integrity sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
@ -5671,7 +5589,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.21, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0:
lodash@4.17.21, lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -5753,13 +5671,6 @@ make-fetch-happen@^9.1.0:
socks-proxy-agent "^6.0.0"
ssri "^8.0.0"
matcher@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz"
integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
dependencies:
escape-string-regexp "^4.0.0"
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz"
@ -6334,14 +6245,6 @@ now-and-later@^2.0.0:
dependencies:
once "^1.3.2"
npm-conf@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz"
integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
dependencies:
config-chain "^1.1.11"
pify "^3.0.0"
npm-run-path@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz"
@ -6906,11 +6809,6 @@ process@~0.11.0:
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@ -6924,11 +6822,6 @@ promise-retry@^2.0.1:
err-code "^2.0.2"
retry "^0.12.0"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
proxy-addr@~2.0.4:
version "2.0.6"
resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz"
@ -7458,18 +7351,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
roarr@^2.15.3:
version "2.15.4"
resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz"
integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
dependencies:
boolean "^3.0.1"
detect-node "^2.0.4"
globalthis "^1.0.1"
json-stringify-safe "^5.0.1"
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@ -7552,11 +7433,6 @@ selenium-webdriver@^4.0.0-alpha.1:
tmp "^0.2.1"
ws "^7.3.1"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
semver-diff@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz"
@ -7574,13 +7450,6 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2:
version "7.3.7"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
semver@^7.3.5, semver@^7.3.7:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
@ -7607,13 +7476,6 @@ send@0.16.2:
range-parser "~1.2.0"
statuses "~1.4.0"
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz"
integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
dependencies:
type-fest "^0.13.1"
serialize-javascript@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz"
@ -7838,11 +7700,6 @@ split2@^4.1.0:
resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz"
integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
@ -8096,13 +7953,6 @@ subarg@^1.0.0:
dependencies:
minimist "^1.1.0"
sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz"
integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
dependencies:
debug "^4.1.0"
supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz"
@ -8476,11 +8326,6 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
@ -8505,11 +8350,6 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
type-fest@^0.13.1:
version "0.13.1"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"

Loading…
Cancel
Save