feat(cli): run migrations and app in start command (#284)

* feat: perform pending migrations in start command

* feat: log error when DB connection is refused

* feat: log error when Redis connection is refused

* refactor: fix type errors

* fix: correct server and copy graphql schema

* fix: differentiate migrations by env

* chore: remove dev executable

* chore: fix typo in default postgresUsername

* fix: copy json files into dist folder

* chore(cli): add dev script

* chore: pull non-dev logs to info level

* feat(cli): run app in start command

* fix(backend): remove default count in Connection

* fix(cli): remove .eslintrc usage in lint script

* refactor: remove disableMigrationsListValidation

* refactor: make Step optional in ExecutionStep

* refactor: make Flow optional in Step
This commit is contained in:
Ali BARIN
2022-04-08 11:27:46 +02:00
committed by GitHub
parent 45f810b5b8
commit 75eda7f2af
34 changed files with 320 additions and 143 deletions

View File

@@ -1,5 +1,5 @@
import User from '../../src/models/user';
import '../../src/config/database';
import '../../src/config/orm';
import logger from '../../src/helpers/logger';
const userParams = {

View File

@@ -1,5 +1,7 @@
import appConfig from './src/config/app';
const fileExtension = appConfig.isDev ? 'ts' : 'js';
const knexConfig = {
client: 'pg',
connection: {
@@ -13,6 +15,8 @@ const knexConfig = {
pool: { min: 0, max: 20 },
migrations: {
directory: __dirname + '/src/db/migrations',
extension: fileExtension,
loadExtensions: [`.${fileExtension}`],
},
seeds: {
directory: __dirname + '/src/db/seeds',

View File

@@ -3,10 +3,10 @@
"version": "0.1.0",
"description": "> TODO: description",
"scripts": {
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/app.ts",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/server.ts",
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
"build": "tsc",
"start": "node dist/src/app.js",
"build": "tsc && yarn copy-statics",
"start": "node dist/src/server.js",
"test": "ava",
"lint": "eslint . --ignore-path ../../.eslintignore",
"db:create": "ts-node ./bin/database/create.ts",
@@ -14,7 +14,8 @@
"db:drop": "ts-node ./bin/database/drop.ts",
"db:migration:create": "knex migrate:make",
"db:rollback": "knex migrate:rollback",
"db:migrate": "knex migrate:latest"
"db:migrate": "knex migrate:latest",
"copy-statics": "copyfiles src/**/*.{graphql,json} dist"
},
"dependencies": {
"@automatisch/web": "0.1.0",
@@ -27,6 +28,7 @@
"axios": "0.24.0",
"bcrypt": "^5.0.1",
"bullmq": "^1.76.1",
"copyfiles": "^2.4.1",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"debug": "~2.6.9",
@@ -60,7 +62,7 @@
}
],
"homepage": "https://github.com/automatisch/automatisch#readme",
"main": "src/app.ts",
"main": "dist/src/app",
"directories": {
"src": "src",
"test": "__tests__"
@@ -78,6 +80,7 @@
"devDependencies": {
"@automatisch/types": "0.1.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
"@types/crypto-js": "^4.0.2",
"@types/express": "^4.17.13",

View File

@@ -4,12 +4,11 @@ import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import corsOptions from './config/cors-options';
import graphQLInstance from './helpers/graphql-instance';
import logger from './helpers/logger';
import morgan from './helpers/morgan';
import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler';
import errorHandler from './helpers/error-handler';
import './config/database';
import './config/orm';
import {
createBullBoardHandler,
serverAdapter,
@@ -21,7 +20,6 @@ if (appConfig.appEnv === 'development') {
}
const app = express();
const port = appConfig.port;
if (appConfig.appEnv === 'development') {
injectBullBoardHandler(app, serverAdapter);
@@ -44,6 +42,4 @@ app.use(function (req: Request, res: Response, next: NextFunction) {
app.use(errorHandler);
app.listen(port, () => {
logger.info(`Server is listening on ${port}`);
});
export default app;

View File

@@ -5,15 +5,16 @@ type AppConfig = {
host: string;
protocol: string;
port: string;
webAppUrl?: string;
webAppUrl: string;
appEnv: string;
isDev: boolean;
postgresDatabase: string;
postgresPort: number;
postgresHost: string;
postgresUsername: string;
postgresPassword: string;
postgresPassword?: string;
postgresEnableSsl: boolean;
baseUrl?: string;
baseUrl: string;
encryptionKey: string;
appSecretKey: string;
serveWebAppSeparately: boolean;
@@ -21,37 +22,44 @@ type AppConfig = {
redisPort: number;
};
const host = process.env.HOST || 'localhost';
const protocol = process.env.PROTOCOL || 'http';
const port = process.env.PORT || '3000';
const serveWebAppSeparately = process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let webAppUrl = `${protocol}://${host}:${port}`;
if (serveWebAppSeparately) {
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
}
const baseUrl = `${protocol}://${host}:${port}`;
const appEnv = process.env.APP_ENV || 'development';
const appConfig: AppConfig = {
host: process.env.HOST || 'localhost',
protocol: process.env.PROTOCOL || 'http',
port: process.env.PORT || '3000',
appEnv: process.env.APP_ENV || 'development',
host,
protocol,
port,
appEnv: appEnv,
isDev: appEnv === 'development',
postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development',
postgresPort: parseInt(process.env.POSTGRES_PORT) || 5432,
postgresPort: parseInt(process.env.POSTGRES_PORT|| '5432'),
postgresHost: process.env.POSTGRES_HOST || 'localhost',
postgresUsername:
process.env.POSTGRES_USERNAME || 'automatish_development_user',
process.env.POSTGRES_USERNAME || 'automatisch_development_user',
postgresPassword: process.env.POSTGRES_PASSWORD,
postgresEnableSsl: process.env.POSTGRES_ENABLE_SSL === 'true' ? true : false,
encryptionKey: process.env.ENCRYPTION_KEY,
appSecretKey: process.env.APP_SECRET_KEY,
serveWebAppSeparately:
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false,
encryptionKey: process.env.ENCRYPTION_KEY || '',
appSecretKey: process.env.APP_SECRET_KEY || '',
serveWebAppSeparately,
redisHost: process.env.REDIS_HOST || '127.0.0.1',
redisPort: parseInt(process.env.REDIS_PORT) || 6379,
redisPort: parseInt(process.env.REDIS_PORT || '6379'),
baseUrl,
webAppUrl,
};
if (appConfig.serveWebAppSeparately) {
appConfig.webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
} else {
appConfig.webAppUrl = `${appConfig.protocol}://${appConfig.host}:${appConfig.port}`;
}
if (!appConfig.encryptionKey) {
throw new Error('ENCRYPTION_KEY environment variable needs to be set!');
}
const baseUrl = `${appConfig.protocol}://${appConfig.host}:${appConfig.port}`;
appConfig.baseUrl = baseUrl;
export default appConfig;

View File

@@ -1,6 +1,19 @@
import { Model } from 'objection';
import knexInstance from 'knex';
import process from 'process';
import knex from 'knex';
import type { Knex } from 'knex';
import knexConfig from '../../knexfile';
import logger from '../helpers/logger';
const knex = knexInstance(knexConfig)
Model.knex(knex)
const knexInstance: Knex = knex(knexConfig);
const CONNECTION_REFUSED = 'ECONNREFUSED';
knexInstance.raw('SELECT 1')
.catch((err) => {
if (err.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed PostgreSQL and it is running.', err);
process.exit();
}
});
export default knexInstance;

View File

@@ -0,0 +1,4 @@
import { Model } from 'objection';
import database from './database';
Model.knex(database)

View File

@@ -3,6 +3,7 @@ import appConfig from './app';
const redisConfig = {
host: appConfig.redisHost,
port: appConfig.redisPort,
enableOfflineQueue: false,
};
export default redisConfig;

View File

@@ -22,6 +22,8 @@ const createAuthData = async (
const appClass = (await import(`../../apps/${connection.key}`)).default;
const appData = App.findOneByKey(connection.key);
if (!connection.formattedData) { return null; }
const appInstance = new appClass(appData, {
consumerKey: connection.formattedData.consumerKey,
consumerSecret: connection.formattedData.consumerSecret,

View File

@@ -19,6 +19,8 @@ const deleteStep = async (
})
.throwIfNotFound();
if (!step) return;
await step.$query().delete();
const nextSteps = await step.flow

View File

@@ -18,6 +18,8 @@ const resetConnection = async (
})
.throwIfNotFound();
if (!connection.formattedData) { return null; }
connection = await connection.$query().patchAndFetch({
formattedData: { screenName: connection.formattedData.screenName },
});

View File

@@ -26,10 +26,13 @@ const getConnectedApps = async (
.filter((app: IApp) => connectionKeys.includes(app.key))
.map((app: IApp) => {
const connection = connections.find(
(connection: IConnection) => connection.key === app.key
(connection) => (connection as IConnection).key === app.key
);
app.connectionCount = connection.count;
if (connection) {
app.connectionCount = connection.count;
}
return app;
});

View File

@@ -12,8 +12,12 @@ const getData = async (_parent: unknown, params: Params, context: Context) => {
.withGraphFetched('connection')
.findById(params.stepId);
if (!step) return null;
const connection = step.connection;
if (!connection || !step.appKey) return null;
const appData = App.findOneByKey(step.appKey);
const AppClass = (await import(`../../apps/${step.appKey}`)).default;

View File

@@ -10,7 +10,7 @@ const levels = {
}
const level = () => {
return appConfig.appEnv === 'development' ? 'debug' : 'warn'
return appConfig.appEnv === 'development' ? 'debug' : 'info'
}
const colors = {

View File

@@ -1,5 +1,6 @@
import fs from 'fs';
import { dirname, join } from 'path';
import { IApp } from '@automatisch/types';
import appInfoConverter from '../helpers/app-info-converter';
class App {
@@ -7,7 +8,7 @@ class App {
static folderPath = join(dirname(this.backendPath), 'apps');
static list = fs.readdirSync(this.folderPath);
static findAll(name?: string): object[] {
static findAll(name?: string): IApp[] {
if (!name) return this.list.map((name) => this.findOneByName(name));
return this.list
@@ -15,7 +16,7 @@ class App {
.map((name) => this.findOneByName(name));
}
static findOneByName(name: string): object {
static findOneByName(name: string): IApp {
const rawAppData = fs.readFileSync(
this.folderPath + `/${name}/info.json`,
'utf-8'
@@ -23,7 +24,7 @@ class App {
return appInfoConverter(rawAppData);
}
static findOneByKey(key: string): object {
static findOneByKey(key: string): IApp {
const rawAppData = fs.readFileSync(
this.folderPath + `/${key}/info.json`,
'utf-8'

View File

@@ -9,10 +9,10 @@ import { IJSONObject } from '@automatisch/types';
class Connection extends Base {
id!: string;
key!: string;
data: string;
formattedData!: IJSONObject;
data = '';
formattedData?: IJSONObject;
userId!: string;
verified: boolean;
verified = false;
count: number;
static tableName = 'connections';
@@ -50,7 +50,7 @@ class Connection extends Base {
appConfig.encryptionKey
).toString();
delete this['formattedData'];
delete this.formattedData;
}
decryptData(): void {

View File

@@ -8,7 +8,7 @@ class ExecutionStep extends Base {
stepId!: string;
dataIn!: Record<string, unknown>;
dataOut!: Record<string, unknown>;
status: string;
status = 'failure';
step: Step;
static tableName = 'execution_steps';

View File

@@ -5,8 +5,8 @@ import ExecutionStep from './execution-step';
class Execution extends Base {
id!: string;
flowId!: string;
testRun: boolean;
executionSteps: ExecutionStep[];
testRun = false;
executionSteps: ExecutionStep[] = [];
static tableName = 'executions';

View File

@@ -6,9 +6,9 @@ import Execution from './execution';
class Flow extends Base {
id!: string;
name: string;
name!: string;
userId!: string;
active: boolean;
active = false;
steps?: [Step];
static tableName = 'flows';

View File

@@ -7,15 +7,15 @@ import type { IStep } from '@automatisch/types';
class Step extends Base {
id!: string;
flowId!: string;
key: string;
appKey: string;
key?: string;
appKey?: string;
type!: IStep["type"];
connectionId?: string;
status: string;
position: number;
parameters: Record<string, unknown>;
status = 'incomplete';
position!: number;
parameters: Record<string, unknown> = {};
connection?: Connection;
flow?: Flow;
flow: Flow;
executionSteps?: [ExecutionStep];
static tableName = 'steps';

View File

@@ -1,11 +1,22 @@
import process from 'process';
import { Queue, QueueScheduler } from 'bullmq';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
const CONNECTION_REFUSED = 'ECONNREFUSED';
const redisConnection = {
connection: redisConfig,
};
new QueueScheduler('processor', redisConnection);
const processorQueue = new Queue('processor', redisConnection);
new QueueScheduler('processor', redisConnection);
processorQueue.on('error', (err) => {
if ((err as any).code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err);
process.exit();
}
});
export default processorQueue;

View File

@@ -0,0 +1,9 @@
import app from './app';
import appConfig from './config/app';
import logger from './helpers/logger';
const port = appConfig.port;
app.listen(port, () => {
logger.info(`Server is listening on ${port}`);
});

View File

@@ -14,8 +14,8 @@ type ProcessorOptions = {
class Processor {
flow: Flow;
untilStep: Step;
testRun: boolean;
untilStep?: Step;
testRun?: boolean;
static variableRegExp = /({{step\..+\..+}})/g;
@@ -32,7 +32,8 @@ class Processor {
.orderBy('position', 'asc');
const triggerStep = steps.find((step) => step.type === 'trigger');
let initialTriggerData = await this.getInitialTriggerData(triggerStep);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let initialTriggerData = await this.getInitialTriggerData(triggerStep!);
if (this.testRun) {
initialTriggerData = [initialTriggerData[0]];
@@ -53,6 +54,8 @@ class Processor {
let fetchedActionData = {};
for await (const step of steps) {
if (!step.appKey) continue;
const appData = App.findOneByKey(step.appKey);
const {
@@ -74,11 +77,11 @@ class Processor {
const appInstance = new AppClass(
appData,
connection.formattedData,
connection?.formattedData,
computedParameters
);
if (!isTrigger) {
if (!isTrigger && key) {
const command = appInstance.actions[key];
fetchedActionData = await command.run();
}
@@ -107,17 +110,21 @@ class Processor {
.orderBy('created_at', 'desc')
.first();
return lastExecutionStepFromFirstExecution.dataOut;
return lastExecutionStepFromFirstExecution?.dataOut;
}
async getInitialTriggerData(step: Step) {
if (!step.appKey) return null;
const appData = App.findOneByKey(step.appKey);
const { appKey, connection, key, parameters: rawParameters = {} } = step;
if (!key) return null;
const AppClass = (await import(`../apps/${appKey}`)).default;
const appInstance = new AppClass(
appData,
connection.formattedData,
connection?.formattedData,
rawParameters
);
@@ -149,30 +156,34 @@ class Processor {
executionSteps: ExecutionSteps
): Step['parameters'] {
const entries = Object.entries(parameters);
return entries.reduce((result, [key, value]: [string, string]) => {
const parts = value.split(Processor.variableRegExp);
return entries.reduce((result, [key, value]: [string, unknown]) => {
if (typeof value === 'string') {
const parts = value.split(Processor.variableRegExp);
const computedValue = parts
.map((part: string) => {
const isVariable = part.match(Processor.variableRegExp);
if (isVariable) {
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '') as string;
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
const keyPath = keyPaths.join('.');
const executionStep = executionSteps[stepId.toString() as string];
const data = executionStep?.dataOut;
const dataValue = get(data, keyPath);
return dataValue;
}
const computedValue = parts
.map((part: string) => {
const isVariable = part.match(Processor.variableRegExp);
if (isVariable) {
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '') as string;
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
const keyPath = keyPaths.join('.');
const executionStep = executionSteps[stepId.toString() as string];
const data = executionStep?.dataOut;
const dataValue = get(data, keyPath);
return dataValue;
}
return part;
})
.join('');
return part;
})
.join('');
return {
...result,
[key]: computedValue,
};
return {
...result,
[key]: computedValue,
};
}
return result;
}, {});
}
}

View File

@@ -1,10 +1,9 @@
import type { IApp } from '@automatisch/types';
import JSONObject from './json-object';
import type { IApp, IJSONObject } from '@automatisch/types';
export default interface AuthenticationInterface {
appData: IApp;
connectionData: IJSONObject;
client: unknown;
verifyCredentials(): Promise<JSONObject>;
verifyCredentials(): Promise<IJSONObject>;
isStillVerified(): Promise<boolean>;
}

View File

@@ -1,2 +1,2 @@
import './config/database';
import './config/orm';
import './workers/processor';

View File

@@ -1,19 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"declaration": true,
"esModuleInterop": true,
"lib": ["es2021"],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"outDir": "dist",
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
},
"skipLibCheck": true,
"sourceMap": true,
"target": "es2021",
"typeRoots": [
"node_modules/@types",
"./src/types",
@@ -21,6 +24,12 @@
]
},
"include": [
"src/**/*"
]
"src/**/*",
"bin/**/*"
],
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}

View File

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env node
const oclif = require('@oclif/core')
const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'
require('ts-node').register({project})
// In dev mode, always show stack traces
oclif.settings.debug = true;
// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)

View File

@@ -1,3 +0,0 @@
@echo off
node "%~dp0\dev" %*

View File

@@ -24,16 +24,19 @@
},
"scripts": {
"build": "shx rm -rf dist && tsc -b",
"lint": "eslint . --ext .ts --config .eslintrc",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'shx rm -rf dist && tsc -b' --ext 'ts'",
"lint": "eslint . --ext .ts --ignore-path ../../.eslintignore",
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
"version": "oclif readme && git add README.md"
},
"dependencies": {
"@automatisch/backend": "^0.1.0",
"@oclif/core": "^1",
"@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1"
"@oclif/plugin-plugins": "^2.0.1",
"dotenv": "^10.0.0"
},
"devDependencies": {
"@oclif/test": "^2",
@@ -46,7 +49,7 @@
"shx": "^0.3.3",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
"typescript": "^4.6.3"
},
"oclif": {
"bin": "automatisch",

View File

@@ -1,9 +1,56 @@
import { Command } from '@oclif/core';
import { Command, Flags } from '@oclif/core';
import * as dotenv from 'dotenv';
export default class Start extends Command {
static description = 'Say hello world';
static description = 'Run automatisch';
static flags = {
env: Flags.string({
multiple: true,
char: 'e',
}),
'env-file': Flags.string(),
}
async prepareEnvVars(): Promise<void> {
const { flags } = await this.parse(Start);
if (flags['env-file']) {
dotenv.config({ path: flags['env-file'] });
}
if (flags.env) {
for (const env of flags.env) {
const [key, value] = env.split('=');
process.env[key] = value;
}
}
delete process.env.SERVE_WEB_APP_SEPARATELY;
}
async runMigrationsIfNeeded(): Promise<void> {
const database = (await import('@automatisch/backend/dist/src/config/database')).default;
const migrator = database.migrate;
const [, pendingMigrations] = await migrator.list();
const pendingMigrationsCount = pendingMigrations.length;
const needsToMigrate = pendingMigrationsCount > 0;
if (needsToMigrate) {
await migrator.latest();
}
}
async runApp(): Promise<void> {
await import('@automatisch/backend/dist/src/server');
}
async run(): Promise<void> {
this.log('hello world from start script');
await this.prepareEnvVars();
await this.runMigrationsIfNeeded();
await this.runApp();
}
}

View File

@@ -1,12 +1,17 @@
{
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"importHelpers": true,
"lib": ["es2021"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "src",
"skipLibCheck": true,
"strict": true,
"target": "es2019",
"target": "es2021",
"traceResolution": false,
"typeRoots": ["node_modules/@types", "./src/types"]
},
"include": ["src/**/*"]

View File

@@ -35,7 +35,7 @@
"slate": "^0.72.8",
"slate-history": "^0.66.0",
"slate-react": "^0.72.9",
"typescript": "^4.1.2",
"typescript": "^4.6.3",
"web-vitals": "^1.0.1",
"yup": "^0.32.11"
},

View File

@@ -4198,6 +4198,14 @@
dependencies:
"@types/node" "*"
"@types/bull@^3.15.8":
version "3.15.8"
resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.8.tgz#ae2139f94490d740b37c8da5d828ce75dd82ce7c"
integrity sha512-8DbSPMSsZH5PWPnGEkAZLYgJEH4ghHJNKF7LB6Wr5R0/v6g+Vs+JoaA7kcvLtHE936xg2WpFPkaoaJgExOmKDw==
dependencies:
"@types/ioredis" "*"
"@types/redis" "^2.8.0"
"@types/chai@*":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc"
@@ -4338,6 +4346,13 @@
dependencies:
"@types/node" "*"
"@types/ioredis@*":
version "4.28.10"
resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.10.tgz#40ceb157a4141088d1394bb87c98ed09a75a06ff"
integrity sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==
dependencies:
"@types/node" "*"
"@types/is-hotkey@^0.1.1":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.7.tgz#30ec6d4234895230b576728ef77e70a52962f3b3"
@@ -4593,6 +4608,13 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/redis@^2.8.0":
version "2.8.32"
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11"
integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==
dependencies:
"@types/node" "*"
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -7120,6 +7142,19 @@ copy-webpack-plugin@^9.0.1:
schema-utils "^3.1.1"
serialize-javascript "^6.0.0"
copyfiles@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5"
integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==
dependencies:
glob "^7.0.5"
minimatch "^3.0.3"
mkdirp "^1.0.4"
noms "0.0.0"
through2 "^2.0.1"
untildify "^4.0.0"
yargs "^16.1.0"
core-js-compat@^3.20.0, core-js-compat@^3.20.2:
version "3.20.3"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.3.tgz#d71f85f94eb5e4bea3407412e549daa083d23bd6"
@@ -9710,7 +9745,7 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@@ -10529,7 +10564,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -12786,6 +12821,13 @@ minimatch@3.0.4, minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.0.3:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
@@ -13205,6 +13247,14 @@ nodemon@^2.0.13:
undefsafe "^2.0.5"
update-notifier "^5.1.0"
noms@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=
dependencies:
inherits "^2.0.1"
readable-stream "~1.0.31"
nopt@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
@@ -15803,6 +15853,16 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@~1.0.31:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
@@ -17223,6 +17283,11 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -17694,7 +17759,7 @@ throat@^6.0.1:
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
through2@^2.0.0:
through2@^2.0.0, through2@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
@@ -18108,15 +18173,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^4.1.2:
version "4.5.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
typescript@^4.4.3:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
typescript@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
ua-parser-js@^0.7.30:
version "0.7.31"
@@ -19351,7 +19411,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs@^16.2.0:
yargs@^16.1.0, yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==