diff --git a/packages/backend/bin/database/seed-user.ts b/packages/backend/bin/database/seed-user.ts index 2d75d7b2..c2a06e1c 100644 --- a/packages/backend/bin/database/seed-user.ts +++ b/packages/backend/bin/database/seed-user.ts @@ -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 = { diff --git a/packages/backend/knexfile.ts b/packages/backend/knexfile.ts index 9f6c353c..4822d4c4 100644 --- a/packages/backend/knexfile.ts +++ b/packages/backend/knexfile.ts @@ -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', diff --git a/packages/backend/package.json b/packages/backend/package.json index 8ccac2d9..d7f1ec49 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -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", diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 1fd02c92..ccf1848c 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -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; diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 7a2bf655..9f7b6017 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -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; diff --git a/packages/backend/src/config/database.ts b/packages/backend/src/config/database.ts index 431a013a..cf55a2fb 100644 --- a/packages/backend/src/config/database.ts +++ b/packages/backend/src/config/database.ts @@ -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; diff --git a/packages/backend/src/config/orm.ts b/packages/backend/src/config/orm.ts new file mode 100644 index 00000000..a154fad3 --- /dev/null +++ b/packages/backend/src/config/orm.ts @@ -0,0 +1,4 @@ +import { Model } from 'objection'; +import database from './database'; + +Model.knex(database) diff --git a/packages/backend/src/config/redis.ts b/packages/backend/src/config/redis.ts index c0fbf994..dd816d0f 100644 --- a/packages/backend/src/config/redis.ts +++ b/packages/backend/src/config/redis.ts @@ -3,6 +3,7 @@ import appConfig from './app'; const redisConfig = { host: appConfig.redisHost, port: appConfig.redisPort, + enableOfflineQueue: false, }; export default redisConfig; diff --git a/packages/backend/src/graphql/mutations/create-auth-data.ts b/packages/backend/src/graphql/mutations/create-auth-data.ts index fa770b6c..051f1de0 100644 --- a/packages/backend/src/graphql/mutations/create-auth-data.ts +++ b/packages/backend/src/graphql/mutations/create-auth-data.ts @@ -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, diff --git a/packages/backend/src/graphql/mutations/delete-step.ts b/packages/backend/src/graphql/mutations/delete-step.ts index 705959f6..bc1c1eb4 100644 --- a/packages/backend/src/graphql/mutations/delete-step.ts +++ b/packages/backend/src/graphql/mutations/delete-step.ts @@ -19,6 +19,8 @@ const deleteStep = async ( }) .throwIfNotFound(); + if (!step) return; + await step.$query().delete(); const nextSteps = await step.flow diff --git a/packages/backend/src/graphql/mutations/reset-connection.ts b/packages/backend/src/graphql/mutations/reset-connection.ts index 06113673..e85f3bcb 100644 --- a/packages/backend/src/graphql/mutations/reset-connection.ts +++ b/packages/backend/src/graphql/mutations/reset-connection.ts @@ -18,6 +18,8 @@ const resetConnection = async ( }) .throwIfNotFound(); + if (!connection.formattedData) { return null; } + connection = await connection.$query().patchAndFetch({ formattedData: { screenName: connection.formattedData.screenName }, }); diff --git a/packages/backend/src/graphql/queries/get-connected-apps.ts b/packages/backend/src/graphql/queries/get-connected-apps.ts index 2e1615ab..6a373dbf 100644 --- a/packages/backend/src/graphql/queries/get-connected-apps.ts +++ b/packages/backend/src/graphql/queries/get-connected-apps.ts @@ -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; }); diff --git a/packages/backend/src/graphql/queries/get-data.ts b/packages/backend/src/graphql/queries/get-data.ts index 68907ea6..89379b1e 100644 --- a/packages/backend/src/graphql/queries/get-data.ts +++ b/packages/backend/src/graphql/queries/get-data.ts @@ -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; diff --git a/packages/backend/src/helpers/logger.ts b/packages/backend/src/helpers/logger.ts index 32371ed2..c206bff5 100644 --- a/packages/backend/src/helpers/logger.ts +++ b/packages/backend/src/helpers/logger.ts @@ -10,7 +10,7 @@ const levels = { } const level = () => { - return appConfig.appEnv === 'development' ? 'debug' : 'warn' + return appConfig.appEnv === 'development' ? 'debug' : 'info' } const colors = { diff --git a/packages/backend/src/models/app.ts b/packages/backend/src/models/app.ts index 08cf6d2a..9b139aac 100644 --- a/packages/backend/src/models/app.ts +++ b/packages/backend/src/models/app.ts @@ -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' diff --git a/packages/backend/src/models/connection.ts b/packages/backend/src/models/connection.ts index 28ee9f43..997b7ffd 100644 --- a/packages/backend/src/models/connection.ts +++ b/packages/backend/src/models/connection.ts @@ -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 { diff --git a/packages/backend/src/models/execution-step.ts b/packages/backend/src/models/execution-step.ts index cebcd754..9b0c701e 100644 --- a/packages/backend/src/models/execution-step.ts +++ b/packages/backend/src/models/execution-step.ts @@ -8,7 +8,7 @@ class ExecutionStep extends Base { stepId!: string; dataIn!: Record; dataOut!: Record; - status: string; + status = 'failure'; step: Step; static tableName = 'execution_steps'; diff --git a/packages/backend/src/models/execution.ts b/packages/backend/src/models/execution.ts index 7cd11029..d08a5e83 100644 --- a/packages/backend/src/models/execution.ts +++ b/packages/backend/src/models/execution.ts @@ -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'; diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index 563c6812..d489ee50 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -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'; diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts index fec08153..09544669 100644 --- a/packages/backend/src/models/step.ts +++ b/packages/backend/src/models/step.ts @@ -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; + status = 'incomplete'; + position!: number; + parameters: Record = {}; connection?: Connection; - flow?: Flow; + flow: Flow; executionSteps?: [ExecutionStep]; static tableName = 'steps'; diff --git a/packages/backend/src/queues/processor.ts b/packages/backend/src/queues/processor.ts index 7960c54c..14e60c1c 100644 --- a/packages/backend/src/queues/processor.ts +++ b/packages/backend/src/queues/processor.ts @@ -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; diff --git a/packages/backend/src/server.ts b/packages/backend/src/server.ts new file mode 100644 index 00000000..e0c0962c --- /dev/null +++ b/packages/backend/src/server.ts @@ -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}`); +}); diff --git a/packages/backend/src/services/processor.ts b/packages/backend/src/services/processor.ts index a82bca2d..8d50c53a 100644 --- a/packages/backend/src/services/processor.ts +++ b/packages/backend/src/services/processor.ts @@ -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; }, {}); } } diff --git a/packages/backend/src/types/interfaces/authentication-interface.ts b/packages/backend/src/types/interfaces/authentication-interface.ts index 092cf15e..5f93423b 100644 --- a/packages/backend/src/types/interfaces/authentication-interface.ts +++ b/packages/backend/src/types/interfaces/authentication-interface.ts @@ -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; + verifyCredentials(): Promise; isStillVerified(): Promise; } diff --git a/packages/backend/src/worker.ts b/packages/backend/src/worker.ts index 3c9640b6..6e4f7ba0 100644 --- a/packages/backend/src/worker.ts +++ b/packages/backend/src/worker.ts @@ -1,2 +1,2 @@ -import './config/database'; +import './config/orm'; import './workers/processor'; diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index f6a5944d..8485fa63 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -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" + } + } } diff --git a/packages/cli/.eslintrc b/packages/cli/.eslintrc deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/cli/bin/dev b/packages/cli/bin/dev deleted file mode 100755 index bbc3f51d..00000000 --- a/packages/cli/bin/dev +++ /dev/null @@ -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) diff --git a/packages/cli/bin/dev.cmd b/packages/cli/bin/dev.cmd deleted file mode 100644 index 077b57ae..00000000 --- a/packages/cli/bin/dev.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\dev" %* \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index bfbe2a76..f701f33d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 3df91f02..a66bec9c 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -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 { + 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 { + 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 { + await import('@automatisch/backend/dist/src/server'); + } async run(): Promise { - this.log('hello world from start script'); + await this.prepareEnvVars(); + + await this.runMigrationsIfNeeded(); + + await this.runApp(); } } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 3160b24c..c5adb764 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -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/**/*"] diff --git a/packages/web/package.json b/packages/web/package.json index b65cbe61..4d93936f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -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" }, diff --git a/yarn.lock b/yarn.lock index 84843f98..7e3051af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==