diff --git a/.gitignore b/.gitignore index 0e5bcad..cfd13ed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist npm-debug.log.* package-lock.json launch.json -.idea \ No newline at end of file +.idea/* +.DS_Store diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dcb7279 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/README.md b/README.md index c185bfa..f2dd0dc 100644 --- a/README.md +++ b/README.md @@ -71,4 +71,34 @@ http://localhost:8000/samples/fullwindow/?domain=http://localhost:3000/directlin Offline directline doesn't require a token or secret, so don't worry about these fields. -Once everything is running, you should see messages sent in through webchat passed through to your bot and vice versa. Your bot should also be able to set privateConversationData, conversationData and userData as offered by the botbuilder SDKs. \ No newline at end of file +Once everything is running, you should see messages sent in through webchat passed through to your bot and vice versa. Your bot should also be able to set privateConversationData, conversationData and userData as offered by the botbuilder SDKs. + +## Running the app + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# incremental rebuild (webpack) +$ npm run webpack +$ npm run start:hmr + +# production mode +$ npm run start:prod +``` + +## Test + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..0dde5f8 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,5 @@ +{ + "language": "ts", + "collection": "@nestjs/schematics", + "sourceRoot": "src" +} diff --git a/nodemon-debug.json b/nodemon-debug.json new file mode 100644 index 0000000..7caf94b --- /dev/null +++ b/nodemon-debug.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "ts", + "ignore": ["src/**/*.spec.ts"], + "exec": "node --inspect-brk -r ts-node/register src/main.ts" +} \ No newline at end of file diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..583bb42 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "ts", + "ignore": ["src/**/*.spec.ts"], + "exec": "ts-node -r tsconfig-paths/register src/main.ts" +} diff --git a/package.json b/package.json index ef25bf9..283e30f 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,63 @@ { - "name": "offline-directline", - "version": "1.2.4", - "description": "Unofficial offline version of the Bot Framework Directline connector", - "homepage": "https://github.com/ryanvolum/offline_dl", - "main": "dist/bridge.js", + "name": "bb-bridge", + "version": "0.0.1", + "description": "Channel Webchat Microsoft Bot Framework written in NestJS", + "author": "Felipe Moura", + "license": "MIT", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc", - "watch": "tsc -w", - "nodewatch": "nodemon dist/index.js", - "start": "node dist/index.js", - "directline": "node dist/cmdutil.js" - }, - "types": "dist/bridge.d.ts", - "author": "Ryan Volum", - "license": "ISC", - "repository": { - "type": "git", - "url": "https://github.com/ryanvolum/offline_dl" + "format": "prettier --write \"src/**/*.ts\"", + "start": "ts-node -r tsconfig-paths/register src/main.ts", + "start:dev": "nodemon", + "start:debug": "nodemon --config nodemon-debug.json", + "prestart:prod": "rimraf dist && tsc", + "start:prod": "node dist/main.js", + "start:hmr": "node dist/server", + "lint": "tslint -p tsconfig.json -c tslint.json", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:e2e": "jest --config ./test/jest-e2e.json", + "webpack": "webpack --config webpack.config.js" }, "dependencies": { - "@types/dotenv": "^4.0.0", - "@types/isomorphic-fetch": "0.0.34", - "body-parser": "^1.17.2", - "botbuilder": "^3.15.0", - "ejs": "^2.6.1", - "es6-promise": "^4.1.1", - "express": "^4.15.3", - "express-ws": "^4.0.0", - "isomorphic-fetch": "^2.2.1", - "lowdb": "^1.0.0", - "moment": "^2.19.1", - "uuid": "^3.1.0" + "@nestjs/common": "^5.1.0", + "@nestjs/core": "^5.1.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.2.2", + "typescript": "^3.0.1" + }, + "devDependencies": { + "@nestjs/testing": "^5.1.0", + "@types/express": "^4.16.0", + "@types/jest": "^23.3.1", + "@types/node": "^10.7.1", + "@types/supertest": "^2.0.5", + "jest": "^23.5.0", + "nodemon": "^1.18.3", + "prettier": "^1.14.2", + "rimraf": "^2.6.2", + "supertest": "^3.1.0", + "ts-jest": "^23.1.3", + "ts-loader": "^4.4.2", + "ts-node": "^7.0.1", + "tsconfig-paths": "^3.5.0", + "tslint": "5.11.0", + "webpack": "^4.16.5", + "webpack-cli": "^3.1.0", + "webpack-node-externals": "^1.7.2" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "coverageDirectory": "../coverage", + "testEnvironment": "node" } } diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts new file mode 100644 index 0000000..761a2c3 --- /dev/null +++ b/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let app: TestingModule; + + beforeAll(async () => { + app = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + const appController = app.get(AppController); + expect(appController.root()).toBe('Hello World!'); + }); + }); +}); diff --git a/src/app.controller.ts b/src/app.controller.ts new file mode 100644 index 0000000..0ddf862 --- /dev/null +++ b/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Get, Controller } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + root(): string { + return this.appService.root(); + } +} diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..8662803 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts new file mode 100644 index 0000000..2bbd546 --- /dev/null +++ b/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + root(): string { + return 'Hello World!'; + } +} diff --git a/src/main.hmr.ts b/src/main.hmr.ts new file mode 100644 index 0000000..81e8c13 --- /dev/null +++ b/src/main.hmr.ts @@ -0,0 +1,15 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +declare const module: any; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); + + if (module.hot) { + module.hot.accept(); + module.hot.dispose(() => app.close()); + } +} +bootstrap(); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..13cad38 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts new file mode 100644 index 0000000..b3b0256 --- /dev/null +++ b/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/test/jest-e2e.json b/test/jest-e2e.json new file mode 100644 index 0000000..e9d912f --- /dev/null +++ b/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/tsconfig.json b/tsconfig.json index eeac43b..d6e1820 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,23 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "dist", - "noImplicitAny": false, - "sourceMap": true - }, - "files": [ - "src/socket_bridge.ts", - "src/cmdutil.ts", - "src/index.ts" - ] + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "allowSyntheticDefaultImports": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./src" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..3939be9 --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,7 @@ +{ + "extends": "tsconfig.json", + "compilerOptions": { + "types": ["jest", "node"] + }, + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..e66f0a4 --- /dev/null +++ b/tslint.json @@ -0,0 +1,55 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "indent": false, + "member-access": [ + false + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + true, + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..d2aff9f --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,34 @@ +const webpack = require('webpack'); +const path = require('path'); +const nodeExternals = require('webpack-node-externals'); + +module.exports = { + entry: ['webpack/hot/poll?1000', './src/main.hmr.ts'], + watch: true, + target: 'node', + externals: [ + nodeExternals({ + whitelist: ['webpack/hot/poll?1000'], + }), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + mode: "development", + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'server.js', + }, +};