├── index.d.ts ├── index.ts ├── .prettierignore ├── lib ├── router │ ├── index.ts │ └── azure-http.router.ts ├── index.ts ├── adapter │ ├── index.ts │ ├── azure-adapter.ts │ ├── azure-request.ts │ └── azure-reply.ts └── azure-http.adapter.ts ├── .prettierrc ├── schematics ├── install │ ├── files │ │ ├── project │ │ │ ├── host.json │ │ │ ├── __project__ │ │ │ │ ├── sample.dat │ │ │ │ ├── webpack.config.js │ │ │ │ ├── index.ts │ │ │ │ └── function.json │ │ │ ├── proxies.json │ │ │ ├── .funcignore │ │ │ ├── local.settings.json │ │ │ └── __sourceRoot__ │ │ │ │ └── main.azure.ts │ │ └── root │ │ │ ├── host.json │ │ │ ├── main │ │ │ ├── sample.dat │ │ │ ├── index.ts │ │ │ └── function.json │ │ │ ├── .funcignore │ │ │ ├── proxies.json │ │ │ ├── local.settings.json │ │ │ └── __rootDir__ │ │ │ └── main.azure.ts │ ├── schema.ts │ ├── schema.json │ ├── index.ts │ └── index.test.ts └── collection.json ├── .release-it.json ├── index.js ├── renovate.json ├── .npmignore ├── tsconfig.schematics.json ├── jest.json ├── .gitignore ├── tsconfig.json ├── .commitlintrc.json ├── .eslintrc.js ├── LICENSE ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .circleci └── config.yml ├── package.json ├── README.md └── CONTRIBUTING.md /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | schematics/install/files/**/*.ts -------------------------------------------------------------------------------- /lib/router/index.ts: -------------------------------------------------------------------------------- 1 | export * from './azure-http.router'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /schematics/install/files/project/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /schematics/install/files/root/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /schematics/install/files/root/main/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /schematics/install/files/project/__project__/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './azure-http.adapter'; 2 | export * from './router'; 3 | export * from './adapter'; 4 | -------------------------------------------------------------------------------- /lib/adapter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './azure-adapter'; 2 | export * from './azure-reply'; 3 | export * from './azure-request'; 4 | -------------------------------------------------------------------------------- /schematics/install/files/project/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /schematics/install/files/root/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /schematics/install/files/root/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /schematics/install/files/project/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore(): release v${version}" 4 | }, 5 | "github": { 6 | "release": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /schematics/install/files/root/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "", 5 | "FUNCTIONS_WORKER_RUNTIME": "node" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /schematics/install/files/project/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "", 5 | "FUNCTIONS_WORKER_RUNTIME": "node" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | exports.__esModule = true; 6 | __export(require("./dist")); 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "semanticCommits": true, 3 | "packageRules": [{ 4 | "depTypeList": ["devDependencies"], 5 | "automerge": true 6 | }], 7 | "extends": [ 8 | "config:base" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | lib 3 | /index.ts 4 | package-lock.json 5 | tslint.json 6 | .prettierrc 7 | 8 | # schematics 9 | schematics/install/*.ts 10 | schematics/install/express-engine/*.ts 11 | 12 | # misc 13 | .DS_Store -------------------------------------------------------------------------------- /tsconfig.schematics.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "schematics", 4 | "outDir": "./schematics" 5 | }, 6 | "extends": "./tsconfig.json", 7 | "include": ["schematics/**/*"], 8 | "exclude": ["node_modules", "**/*.spec.ts", "./schematics/install/files/**"] 9 | } 10 | -------------------------------------------------------------------------------- /schematics/install/files/project/__project__/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options) { 2 | return { 3 | ...options, 4 | entry: __dirname + '/index.ts', 5 | output: { 6 | libraryTarget: 'commonjs2', 7 | filename: '<%= getProjectName() %>/index.js' 8 | } 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /schematics/install/files/root/main/index.ts: -------------------------------------------------------------------------------- 1 | import { Context, HttpRequest } from '@azure/functions'; 2 | import { AzureHttpAdapter } from '@nestjs/azure-func-http'; 3 | import { createApp } from '../<%= getRootDirectory() %>/main.azure'; 4 | 5 | export default function(context: Context, req: HttpRequest): void { 6 | AzureHttpAdapter.handle(createApp, context, req); 7 | } 8 | -------------------------------------------------------------------------------- /lib/adapter/azure-adapter.ts: -------------------------------------------------------------------------------- 1 | import { AzureRequest } from './azure-request'; 2 | import { AzureReply } from './azure-reply'; 3 | 4 | export function createHandlerAdapter(handler) { 5 | return context => { 6 | context.res = context.res || {}; 7 | const req = new AzureRequest(context); 8 | const res = new AzureReply(context); 9 | handler(req, res); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /schematics/install/files/project/__project__/index.ts: -------------------------------------------------------------------------------- 1 | import { Context, HttpRequest } from '@azure/functions'; 2 | import { AzureHttpAdapter } from '@nestjs/azure-func-http'; 3 | import { createApp } from '../apps/<%= getProjectName() %>/src/main.azure'; 4 | 5 | export default function(context: Context, req: HttpRequest): void { 6 | AzureHttpAdapter.handle(createApp, context, req); 7 | } 8 | -------------------------------------------------------------------------------- /jest.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "moduleFileExtensions": ["js", "ts"], 4 | "rootDir": ".", 5 | "testEnvironment": "node", 6 | "testRegex": ".(test|spec).ts$", 7 | "transform": { 8 | "^.+\\.(t|j)s$": "ts-jest" 9 | }, 10 | "coverageDirectory": "./coverage", 11 | "verbose": true, 12 | "bail": true, 13 | "testPathIgnorePatterns": ["/node_modules/", "files"] 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # IDE 5 | /.idea 6 | /.awcache 7 | /.vscode 8 | 9 | # misc 10 | npm-debug.log 11 | .DS_Store 12 | 13 | # tests 14 | /test 15 | /coverage 16 | /.nyc_output 17 | 18 | # source 19 | dist 20 | 21 | # schematics 22 | schematics/install/*.js 23 | schematics/install/*.d.ts 24 | schematics/install/express-engine/*.js 25 | schematics/install/express-engine/*.d.ts -------------------------------------------------------------------------------- /schematics/install/files/root/main/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "route": "{*segments}" 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ], 16 | "scriptFile": "../dist/main/index.js" 17 | } 18 | -------------------------------------------------------------------------------- /schematics/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "nest-add": { 5 | "description": "Adds Azure Functions HTTP template to the application without affecting any app files", 6 | "factory": "./install", 7 | "schema": "./install/schema.json", 8 | "aliases": ["nest-azure-shell"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /schematics/install/files/project/__project__/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "route": "<%= getProjectName() %>/{*segments}" 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ], 16 | "scriptFile": "../dist/<%= getProjectName() %>/index.js" 17 | } 18 | -------------------------------------------------------------------------------- /schematics/install/files/root/__rootDir__/main.azure.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { <%= getRootModuleName() %> } from './<%= getRootModulePath() %>'; 4 | 5 | export async function createApp(): Promise { 6 | const app = await NestFactory.create(<%= getRootModuleName() %>); 7 | app.setGlobalPrefix('api'); 8 | 9 | await app.init(); 10 | return app; 11 | } 12 | -------------------------------------------------------------------------------- /schematics/install/files/project/__sourceRoot__/main.azure.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { <%= getRootModuleName() %> } from './<%= getRootModulePath() %>'; 4 | 5 | export async function createApp(): Promise { 6 | const app = await NestFactory.create(<%= getRootModuleName() %>); 7 | app.setGlobalPrefix('api/<%= getProjectName() %>'); 8 | 9 | await app.init(); 10 | return app; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "ES2021", 11 | "sourceMap": false, 12 | "outDir": "./dist", 13 | "skipLibCheck": true 14 | }, 15 | "include": ["lib/**/*", "../index.ts"], 16 | "exclude": ["node_modules", "**/*.spec.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /schematics/install/schema.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | /** 3 | * Application root directory 4 | */ 5 | rootDir?: string; 6 | /** 7 | * The name of the root module file 8 | */ 9 | rootModuleFileName?: string; 10 | /** 11 | * The name of the root module class. 12 | */ 13 | rootModuleClassName?: string; 14 | /** 15 | * Skip installing dependency packages. 16 | */ 17 | skipInstall?: boolean; 18 | /** 19 | * . 20 | */ 21 | sourceRoot?: string; 22 | /** 23 | * The project where generate the azure files. 24 | */ 25 | project?: string; 26 | } 27 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-angular"], 3 | "rules": { 4 | "subject-case": [ 5 | 2, 6 | "always", 7 | ["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case"] 8 | ], 9 | "type-enum": [ 10 | 2, 11 | "always", 12 | [ 13 | "build", 14 | "chore", 15 | "ci", 16 | "docs", 17 | "feat", 18 | "fix", 19 | "perf", 20 | "refactor", 21 | "revert", 22 | "style", 23 | "test", 24 | "sample" 25 | ] 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module' 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier' 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true 17 | }, 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off' 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/adapter/azure-request.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | export class AzureRequest extends Readable { 4 | readonly url: string; 5 | readonly context: Record; 6 | readonly originalUrl: string; 7 | readonly headers: Record; 8 | readonly body: any; 9 | 10 | constructor(context: Record) { 11 | super(); 12 | 13 | Object.assign(this, context.req); 14 | this.context = context; 15 | this.url = this.originalUrl; 16 | this.headers = this.headers || {}; 17 | 18 | // Recreate original request stream from body 19 | const body = Buffer.isBuffer(context.req.body) 20 | ? context.req.body 21 | : context.req.rawBody; 22 | 23 | if (body !== null && body !== undefined) { 24 | this.push(body); 25 | } 26 | // Close the stream 27 | this.push(null); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2021 Kamil Mysliwiec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /schematics/install/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "SchematicsNestEngineInstall", 4 | "title": "Nest Engine Install Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "rootDir": { 8 | "type": "string", 9 | "description": "Application root directory.", 10 | "default": "src" 11 | }, 12 | "rootModuleFileName": { 13 | "type": "string", 14 | "format": "path", 15 | "description": "The name of the root module file (without extension)", 16 | "default": "app.module" 17 | }, 18 | "rootModuleClassName": { 19 | "type": "string", 20 | "description": "The name of the root module class.", 21 | "default": "AppModule" 22 | }, 23 | "skipInstall": { 24 | "description": "Skip installing dependency packages.", 25 | "type": "boolean", 26 | "default": false 27 | }, 28 | "sourceRoot": { 29 | "type": "string", 30 | "description": "The source root directory." 31 | }, 32 | "project": { 33 | "type": "string", 34 | "description": "The project where generate the azure files." 35 | } 36 | }, 37 | "required": [] 38 | } 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | Please check if your PR fulfills the following requirements: 3 | 4 | - [ ] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md 5 | - [ ] Tests for the changes have been added (for bug fixes / features) 6 | - [ ] Docs have been added / updated (for bug fixes / features) 7 | 8 | 9 | ## PR Type 10 | What kind of change does this PR introduce? 11 | 12 | 13 | - [ ] Bugfix 14 | - [ ] Feature 15 | - [ ] Code style update (formatting, local variables) 16 | - [ ] Refactoring (no functional changes, no api changes) 17 | - [ ] Build related changes 18 | - [ ] CI related changes 19 | - [ ] Other... Please describe: 20 | 21 | ## What is the current behavior? 22 | 23 | 24 | Issue Number: N/A 25 | 26 | 27 | ## What is the new behavior? 28 | 29 | 30 | ## Does this PR introduce a breaking change? 31 | - [ ] Yes 32 | - [ ] No 33 | 34 | 35 | 36 | 37 | ## Other information 38 | -------------------------------------------------------------------------------- /lib/azure-http.adapter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import { Context, HttpRequest } from '@azure/functions'; 3 | import { HttpServer, INestApplication } from '@nestjs/common'; 4 | import { createHandlerAdapter } from './adapter/azure-adapter'; 5 | import { AzureHttpRouter } from './router'; 6 | 7 | let handler: Function; 8 | 9 | export class AzureHttpAdapterStatic { 10 | handle( 11 | createApp: () => Promise, 12 | context: Context, 13 | req: HttpRequest 14 | ) { 15 | if (handler) { 16 | return handler(context, req); 17 | } 18 | this.createHandler(createApp).then((fn) => fn(context, req)); 19 | } 20 | 21 | private async createHandler( 22 | createApp: () => Promise< 23 | Omit 24 | > 25 | ) { 26 | const app = await createApp(); 27 | const adapter = app.getHttpAdapter(); 28 | if (this.hasGetTypeMethod(adapter) && adapter.getType() === 'azure-http') { 29 | return (adapter as any as AzureHttpRouter).handle.bind(adapter); 30 | } 31 | const instance = app.getHttpAdapter().getInstance(); 32 | handler = createHandlerAdapter(instance); 33 | return handler; 34 | } 35 | 36 | private hasGetTypeMethod( 37 | adapter: HttpServer 38 | ): adapter is HttpServer & { getType: Function } { 39 | return !!(adapter as any).getType; 40 | } 41 | } 42 | 43 | export const AzureHttpAdapter = new AzureHttpAdapterStatic(); 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## I'm submitting a... 8 | 11 |

12 | [ ] Regression 
13 | [ ] Bug report
14 | [ ] Feature request
15 | [ ] Documentation issue or request
16 | [ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
17 | 
18 | 19 | ## Current behavior 20 | 21 | 22 | 23 | ## Expected behavior 24 | 25 | 26 | 27 | ## Minimal reproduction of the problem with instructions 28 | 29 | 30 | ## What is the motivation / use case for changing the behavior? 31 | 32 | 33 | 34 | ## Environment 35 | 36 |

37 | Nest version: X.Y.Z
38 | 
39 |  
40 | For Tooling issues:
41 | - Node version: XX  
42 | - Platform:  
43 | 
44 | Others:
45 | 
46 | 
47 | -------------------------------------------------------------------------------- /lib/adapter/azure-reply.ts: -------------------------------------------------------------------------------- 1 | import { OutgoingMessage } from 'http'; 2 | 3 | export class AzureReply extends OutgoingMessage { 4 | private readonly _headerSent: boolean; 5 | private readonly outputData: { data: any }[]; 6 | statusCode?: number; 7 | 8 | constructor(context: Record) { 9 | super(); 10 | 11 | // Avoid issues when data is streamed out 12 | this._headerSent = true; 13 | 14 | this.writeHead = this.writeHead.bind(this, context); 15 | this.end = this.finish.bind(this, context); 16 | } 17 | 18 | writeHead( 19 | context: Record, 20 | statusCode: number, 21 | statusMessage: string, 22 | headers: Record 23 | ) { 24 | if (statusCode) { 25 | this.statusCode = statusCode; 26 | } 27 | if (headers) { 28 | const keys = Object.keys(headers); 29 | for (const key of keys) { 30 | this.setHeader(key, headers[key]); 31 | } 32 | } 33 | 34 | context.res.status = this.statusCode; 35 | context.res.headers = this.getHeaders() || {}; 36 | } 37 | 38 | finish(context: Record, body: Record | undefined) { 39 | // If data was streamed out, get it back to body 40 | if (body === undefined && this.outputData.length > 0) { 41 | body = Buffer.concat( 42 | this.outputData.map(o => 43 | Buffer.isBuffer(o.data) ? o.data : Buffer.from(o.data) 44 | ) 45 | ); 46 | } 47 | 48 | context.res.status = this.statusCode; 49 | context.res.body = body; 50 | context.done(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | aliases: 4 | - &restore-cache 5 | restore_cache: 6 | name: Restore Yarn Package Cache 7 | keys: 8 | - yarn-packages-{{ checksum "yarn.lock" }} 9 | - &install-deps 10 | run: 11 | name: Install dependencies 12 | command: yarn 13 | - &build-packages 14 | run: 15 | name: Build 16 | command: npm run build 17 | 18 | jobs: 19 | build: 20 | working_directory: ~/nest 21 | docker: 22 | - image: cimg/node:20.3 23 | steps: 24 | - checkout 25 | - restore_cache: 26 | name: Restore Yarn Package Cache 27 | keys: 28 | - yarn-packages-{{ checksum "yarn.lock" }} 29 | - run: 30 | name: Install dependencies 31 | command: yarn 32 | - save_cache: 33 | name: Save Yarn Package Cache 34 | key: yarn-packages-{{ checksum "yarn.lock" }} 35 | paths: 36 | - ~/.cache/yarn 37 | - run: 38 | name: Build 39 | command: npm run build 40 | 41 | unit_tests: 42 | working_directory: ~/nest 43 | docker: 44 | - image: cimg/node:20.3 45 | steps: 46 | - checkout 47 | - restore_cache: 48 | name: Restore Yarn Package Cache 49 | keys: 50 | - yarn-packages-{{ checksum "yarn.lock" }} 51 | - run: 52 | name: Install dependencies 53 | command: yarn 54 | - save_cache: 55 | name: Save Yarn Package Cache 56 | key: yarn-packages-{{ checksum "yarn.lock" }} 57 | paths: 58 | - ~/.cache/yarn 59 | - run: 60 | name: Tests 61 | command: npm run test 62 | 63 | workflows: 64 | version: 2 65 | build-and-test: 66 | jobs: 67 | - build 68 | - unit_tests: 69 | requires: 70 | - build 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs/azure-func-http", 3 | "version": "0.10.0", 4 | "description": "Nest - modern, fast, powerful node.js web framework (@azure-func-http)", 5 | "author": "Kamil Mysliwiec", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "npm run build:lib && npm run build:schematics", 9 | "build:lib": "tsc -p tsconfig.json", 10 | "build:schematics": "tsc -p tsconfig.schematics.json", 11 | "lint": "eslint --ext ts --fix lib", 12 | "format": "prettier --write \"lib/**/*.ts\"", 13 | "precommit": "lint-staged", 14 | "prepublish:npm": "npm run build", 15 | "publish:npm": "npm publish --access public", 16 | "prepublish:next": "npm run build", 17 | "publish:next": "npm publish --access public --tag next", 18 | "prerelease": "npm run build", 19 | "release": "release-it", 20 | "test": "jest -w 1 --no-cache --config jest.json", 21 | "test:dev": "NODE_ENV=test npm run -s test -- --watchAll" 22 | }, 23 | "peerDependencies": { 24 | "@azure/functions": "^1.0.3 || ^2.0.0 || ^3.0.0", 25 | "@nestjs/common": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", 26 | "@nestjs/core": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", 27 | "reflect-metadata": "^0.1.13" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/schematics": "^16.0.0", 31 | "@azure/functions": "3.5.1", 32 | "@commitlint/cli": "19.6.1", 33 | "@commitlint/config-angular": "19.7.0", 34 | "@nestjs/common": "10.2.7", 35 | "@nestjs/core": "10.2.7", 36 | "@nestjs/schematics": "10.0.2", 37 | "@types/node": "22.10.7", 38 | "@types/jest": "29.5.14", 39 | "@typescript-eslint/eslint-plugin": "7.18.0", 40 | "@typescript-eslint/parser": "7.18.0", 41 | "@schematics/angular": "16.2.16", 42 | "eslint": "8.57.1", 43 | "eslint-config-prettier": "10.0.1", 44 | "eslint-plugin-import": "2.31.0", 45 | "husky": "9.1.7", 46 | "lint-staged": "15.4.1", 47 | "prettier": "3.4.2", 48 | "release-it": "17.1.1", 49 | "typescript": "5.7.3", 50 | "jest": "29.7.0", 51 | "ts-jest": "29.2.5" 52 | }, 53 | "dependencies": { 54 | "cors": "2.8.5", 55 | "jsonc-parser": "^3.2.0", 56 | "trouter": "3.2.1" 57 | }, 58 | "schematics": "./schematics/collection.json", 59 | "lint-staged": { 60 | "*.ts": [ 61 | "prettier --write" 62 | ] 63 | }, 64 | "husky": { 65 | "hooks": { 66 | "pre-commit": "lint-staged", 67 | "commit-msg": "commitlint -c .commitlintrc.json -E HUSKY_GIT_PARAMS" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /schematics/install/index.ts: -------------------------------------------------------------------------------- 1 | import { strings } from '@angular-devkit/core'; 2 | import { parse as parseJson } from 'jsonc-parser'; 3 | import { 4 | apply, 5 | chain, 6 | FileEntry, 7 | forEach, 8 | mergeWith, 9 | noop, 10 | Rule, 11 | SchematicContext, 12 | SchematicsException, 13 | template, 14 | Tree, 15 | url 16 | } from '@angular-devkit/schematics'; 17 | import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; 18 | import { 19 | addPackageJsonDependency, 20 | NodeDependencyType 21 | } from '@schematics/angular/utility/dependencies'; 22 | import { Schema as AzureOptions } from './schema'; 23 | 24 | type UpdateJsonFn = (obj: T) => T | void; 25 | 26 | function addDependenciesAndScripts(): Rule { 27 | return (host: Tree) => { 28 | addPackageJsonDependency(host, { 29 | type: NodeDependencyType.Default, 30 | name: '@azure/functions', 31 | version: '^1.0.3' 32 | }); 33 | const pkgPath = '/package.json'; 34 | const buffer = host.read(pkgPath); 35 | if (buffer === null) { 36 | throw new SchematicsException('Could not find package.json'); 37 | } 38 | 39 | const pkg = JSON.parse(buffer.toString()); 40 | pkg.scripts['start:azure'] = 'npm run build && func host start'; 41 | 42 | host.overwrite(pkgPath, JSON.stringify(pkg, null, 2)); 43 | return host; 44 | }; 45 | } 46 | 47 | function updateJsonFile( 48 | host: Tree, 49 | path: string, 50 | callback: UpdateJsonFn 51 | ): Tree { 52 | const source = host.read(path); 53 | if (source) { 54 | const sourceText = source.toString('utf-8'); 55 | const json = parseJson(sourceText); 56 | callback(json as {} as T); 57 | host.overwrite(path, JSON.stringify(json, null, 2)); 58 | } 59 | return host; 60 | } 61 | const applyProjectName = (projectName, host) => { 62 | if (projectName) { 63 | let nestCliFileExists = host.exists('nest-cli.json'); 64 | 65 | if (nestCliFileExists) { 66 | updateJsonFile( 67 | host, 68 | 'nest-cli.json', 69 | (optionsFile: Record) => { 70 | if (optionsFile.projects[projectName].compilerOptions) { 71 | optionsFile.projects[projectName].compilerOptions = { 72 | ...optionsFile.projects[projectName].compilerOptions, 73 | ...{ 74 | webpack: true, 75 | webpackConfigPath: `${projectName}/webpack.config.js` 76 | } 77 | }; 78 | } 79 | } 80 | ); 81 | } 82 | } 83 | }; 84 | 85 | const rootFiles = [ 86 | '/.funcignore', 87 | '/host.json', 88 | '/local.settings.json', 89 | '/proxies.json' 90 | ]; 91 | 92 | const validateExistingRootFiles = (host: Tree, file: FileEntry) => { 93 | return rootFiles.includes(file.path) && host.exists(file.path); 94 | }; 95 | 96 | export default function (options: AzureOptions): Rule { 97 | return (host: Tree, context: SchematicContext) => { 98 | if (!options.skipInstall) { 99 | context.addTask(new NodePackageInstallTask()); 100 | } 101 | const defaultSourceRoot = 102 | options.project !== undefined ? options.sourceRoot : options.rootDir; 103 | const rootSource = apply( 104 | options.project ? url('./files/project') : url('./files/root'), 105 | [ 106 | template({ 107 | ...strings, 108 | ...(options as AzureOptions), 109 | rootDir: options.rootDir, 110 | sourceRoot: defaultSourceRoot, 111 | getRootDirectory: () => options.rootDir, 112 | getProjectName: () => options.project, 113 | stripTsExtension: (s: string) => s.replace(/\.ts$/, ''), 114 | getRootModuleName: () => options.rootModuleClassName, 115 | getRootModulePath: () => options.rootModuleFileName 116 | }), 117 | forEach((file: FileEntry) => { 118 | if (validateExistingRootFiles(host, file)) return null; 119 | return file; 120 | }) 121 | ] 122 | ); 123 | 124 | return chain([ 125 | (tree, context) => 126 | options.project 127 | ? applyProjectName(options.project, host) 128 | : noop()(tree, context), 129 | addDependenciesAndScripts(), 130 | mergeWith(rootSource) 131 | ]); 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /lib/router/azure-http.router.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-empty-function */ 4 | import { 5 | HttpStatus, 6 | InternalServerErrorException, 7 | NotImplementedException, 8 | RequestMethod, 9 | VersioningOptions 10 | } from '@nestjs/common'; 11 | import { VersionValue } from '@nestjs/common/interfaces'; 12 | import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; 13 | import { AbstractHttpAdapter } from '@nestjs/core'; 14 | import { RouterMethodFactory } from '@nestjs/core/helpers/router-method-factory'; 15 | import * as cors from 'cors'; 16 | import TRouter from 'trouter'; 17 | import { AzureReply, AzureRequest } from '../adapter'; 18 | 19 | export class AzureHttpRouter extends AbstractHttpAdapter { 20 | private readonly routerMethodFactory = new RouterMethodFactory(); 21 | 22 | constructor() { 23 | super(new TRouter()); 24 | } 25 | 26 | public handle(context: Record, request: any) { 27 | const req = context.req; 28 | const originalUrl = req.originalUrl as string; 29 | const path = new URL(originalUrl).pathname; 30 | 31 | const { params, handlers } = this.instance.find(req.method, path); 32 | req.params = params; 33 | 34 | if (handlers.length === 0) { 35 | return this.handleNotFound(context, req.method, originalUrl); 36 | } 37 | const azureRequest = new AzureRequest(context); 38 | const azureReply = new AzureReply(context); 39 | const nextRoute = (i = 0) => 40 | handlers[i] && 41 | handlers[i](azureRequest, azureReply, () => nextRoute(i + 1)); 42 | nextRoute(); 43 | } 44 | 45 | public handleNotFound( 46 | context: Record, 47 | method: string, 48 | originalUrl: string 49 | ) { 50 | context.res.status = HttpStatus.NOT_FOUND; 51 | context.res.body = { 52 | statusCode: HttpStatus.NOT_FOUND, 53 | error: `Cannot ${method} ${originalUrl}` 54 | }; 55 | context.done(); 56 | return; 57 | } 58 | 59 | public enableCors(options: CorsOptions) { 60 | this.use(cors(options)); 61 | } 62 | 63 | public reply(response: any, body: any, statusCode?: number) { 64 | response.writeHead(statusCode); 65 | response.end(body); 66 | } 67 | 68 | public status(response: any, statusCode: number) { 69 | response.statusCode = statusCode; 70 | } 71 | 72 | public end(response: any, message?: string) { 73 | return response.end(message); 74 | } 75 | 76 | public getHttpServer(): T { 77 | return this.instance as T; 78 | } 79 | 80 | public getInstance(): T { 81 | return this.instance as T; 82 | } 83 | 84 | public isHeadersSent(response: any): boolean { 85 | return response.headersSent; 86 | } 87 | 88 | public setHeader(response: any, name: string, value: string) { 89 | return response.setHeader(name, value); 90 | } 91 | 92 | public getRequestMethod(request: any): string { 93 | return request.method; 94 | } 95 | 96 | public getRequestUrl(request: any): string { 97 | return request.url; 98 | } 99 | 100 | public getRequestHostname(request: any): string { 101 | return request.hostname; 102 | } 103 | 104 | public createMiddlewareFactory( 105 | requestMethod: RequestMethod 106 | ): (path: string, callback: Function) => any { 107 | return this.routerMethodFactory 108 | .get(this.instance, requestMethod) 109 | .bind(this.instance); 110 | } 111 | 112 | public getType(): string { 113 | return 'azure-http'; 114 | } 115 | 116 | public applyVersionFilter( 117 | handler: Function, 118 | version: VersionValue, 119 | versioningOptions: VersioningOptions 120 | ) { 121 | throw new NotImplementedException(); 122 | return (req, res, next) => { 123 | return () => {}; 124 | }; 125 | } 126 | 127 | public listen(port: any, ...args: any[]) {} 128 | public render(response: any, view: string, options: any) {} 129 | public redirect(response: any, statusCode: number, url: string) {} 130 | public close() {} 131 | public initHttpServer() {} 132 | public useStaticAssets(options: any) {} 133 | public setViewEngine(options: any) {} 134 | public registerParserMiddleware() {} 135 | public setNotFoundHandler(handler: Function) {} 136 | public setErrorHandler(handler: Function) {} 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Coverage 16 | Discord 17 | Backers on Open Collective 18 | Sponsors on Open Collective 19 | 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Azure Functions](https://code.visualstudio.com/tutorials/functions-extension/getting-started) HTTP module for [Nest](https://github.com/nestjs/nest). 28 | 29 | ## Installation 30 | 31 | Using the Nest CLI: 32 | 33 | ```bash 34 | $ nest add @nestjs/azure-func-http 35 | ``` 36 | 37 | Example output: 38 | 39 | ```bash 40 | ✔ Installation in progress... ☕ 41 | CREATE /.funcignore (66 bytes) 42 | CREATE /host.json (23 bytes) 43 | CREATE /local.settings.json (116 bytes) 44 | CREATE /proxies.json (72 bytes) 45 | CREATE /main/function.json (294 bytes) 46 | CREATE /main/index.ts (287 bytes) 47 | CREATE /main/sample.dat (23 bytes) 48 | CREATE /src/main.azure.ts (321 bytes) 49 | UPDATE /package.json (1827 bytes) 50 | ``` 51 | 52 | ## Tutorial 53 | 54 | You can read more about this integration [here](https://trilon.io/blog/deploy-nestjs-azure-functions). 55 | 56 | ## Native routing 57 | 58 | If you don't need the compatibility with `express` library, you can use a native routing instead: 59 | 60 | ```typescript 61 | const app = await NestFactory.create(AppModule, new AzureHttpRouter()); 62 | ``` 63 | 64 | `AzureHttpRouter` is exported from `@nestjs/azure-func-http`. Since `AzureHttpRouter` doesn't use `express` underneath, the routing itself is much faster. 65 | 66 | ## Additional options 67 | 68 | You can pass additional flags to customize the post-install schematic. For example, if your base application directory is different than `src`, use `--rootDir` flag: 69 | 70 | ```bash 71 | $ nest add @nestjs/azure-func-http --rootDir app 72 | ``` 73 | 74 | Other available flags: 75 | 76 | - `rootModuleFileName` - the name of the root module file, default: `app.module` 77 | - `rootModuleClassName` - the name of the root module class, default: `AppModule` 78 | - `skipInstall` - skip installing dependencies, default: `false` 79 | 80 | ## Support 81 | 82 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 83 | 84 | ## Stay in touch 85 | 86 | - Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) 87 | - Website - [https://nestjs.com](https://nestjs.com/) 88 | - Twitter - [@nestframework](https://twitter.com/nestframework) 89 | 90 | ## License 91 | 92 | Nest is [MIT licensed](LICENSE). 93 | -------------------------------------------------------------------------------- /schematics/install/index.test.ts: -------------------------------------------------------------------------------- 1 | import { FileEntry, Tree } from '@angular-devkit/schematics'; 2 | import { 3 | SchematicTestRunner, 4 | UnitTestTree 5 | } from '@angular-devkit/schematics/testing'; 6 | import * as path from 'path'; 7 | import { Schema } from './schema'; 8 | 9 | const getFileContent = (tree: UnitTestTree, path: string): string => { 10 | const fileEntry: FileEntry = tree.get(path); 11 | if (!fileEntry) { 12 | throw new Error(`The file does not exist.`); 13 | } 14 | return fileEntry.content.toString(); 15 | }; 16 | describe('Schematic Tests Nest Add', () => { 17 | let nestTree: Tree; 18 | 19 | const runner: SchematicTestRunner = new SchematicTestRunner( 20 | 'azure-func-http', 21 | path.join(process.cwd(), 'schematics/collection.json') 22 | ); 23 | 24 | beforeEach(async () => { 25 | nestTree = await createTestNest(runner); 26 | }); 27 | 28 | describe('Test for default setup', () => { 29 | it('should add azure func for default setup', async () => { 30 | const options: Schema = { 31 | skipInstall: true, 32 | rootModuleFileName: 'app.module', 33 | rootModuleClassName: 'AppModule' 34 | }; 35 | 36 | const tree = await runner.runSchematic('nest-add', options, nestTree); 37 | const files: string[] = tree.files; 38 | expect(files).toEqual([ 39 | '/.eslintrc.js', 40 | '/.prettierrc', 41 | '/README.md', 42 | '/nest-cli.json', 43 | '/package.json', 44 | '/tsconfig.build.json', 45 | '/tsconfig.json', 46 | '/.funcignore', 47 | '/host.json', 48 | '/local.settings.json', 49 | '/proxies.json', 50 | '/src/app.controller.spec.ts', 51 | '/src/app.controller.ts', 52 | '/src/app.module.ts', 53 | '/src/app.service.ts', 54 | '/src/main.ts', 55 | '/src/main.azure.ts', 56 | '/test/app.e2e-spec.ts', 57 | '/test/jest-e2e.json', 58 | '/main/function.json', 59 | '/main/index.ts', 60 | '/main/sample.dat' 61 | ]); 62 | }); 63 | 64 | it('should have a nest-cli.json for default app', async () => { 65 | const options: Schema = { 66 | sourceRoot: 'src', 67 | skipInstall: true, 68 | rootDir: 'src', 69 | rootModuleFileName: 'app.module', 70 | rootModuleClassName: 'AppModule' 71 | }; 72 | 73 | const tree = await runner.runSchematic('nest-add', options, nestTree); 74 | const fileContent = getFileContent(tree, '/nest-cli.json'); 75 | expect(fileContent).toContain(`"sourceRoot": "src"`); 76 | }); 77 | 78 | it('should import the app.module int main azure file for default app', async () => { 79 | const options: Schema = { 80 | sourceRoot: 'src', 81 | skipInstall: true, 82 | rootDir: 'src', 83 | rootModuleFileName: 'app.module', 84 | rootModuleClassName: 'AppModule' 85 | }; 86 | 87 | const tree = await runner.runSchematic('nest-add', options, nestTree); 88 | const fileContent = getFileContent(tree, '/src/main.azure.ts'); 89 | 90 | expect(fileContent).toContain( 91 | `import { AppModule } from './app.module';` 92 | ); 93 | }); 94 | 95 | it('should have the root dir for index file in main azure dir for default app', async () => { 96 | const options: Schema = { 97 | sourceRoot: 'src', 98 | skipInstall: true, 99 | rootDir: 'src', 100 | rootModuleFileName: 'app.module', 101 | rootModuleClassName: 'AppModule' 102 | }; 103 | 104 | const tree = await runner.runSchematic('nest-add', options, nestTree); 105 | const fileContent = getFileContent(tree, '/main/index.ts'); 106 | 107 | expect(fileContent).toContain( 108 | `import { createApp } from '../src/main.azure';` 109 | ); 110 | }); 111 | 112 | it('should not import the webpack config for a default app', async () => { 113 | const options: Schema = { 114 | sourceRoot: 'src', 115 | skipInstall: true, 116 | rootDir: 'src', 117 | rootModuleFileName: 'app.module', 118 | rootModuleClassName: 'AppModule' 119 | }; 120 | 121 | const tree = await runner.runSchematic('nest-add', options, nestTree); 122 | const fileContent = tree.get('webpack.config.js'); 123 | 124 | expect(fileContent).toBeNull(); 125 | }); 126 | }); 127 | 128 | describe('Tests for monorepo', () => { 129 | it('should add azure-func for monorepo app', async () => { 130 | const projectName = 'azure-2'; 131 | const options: Schema = { 132 | skipInstall: true, 133 | project: projectName, 134 | rootDir: `apps/${projectName}`, 135 | sourceRoot: `apps/${projectName}/src` 136 | }; 137 | 138 | await runner.runExternalSchematic( 139 | '@nestjs/schematics', 140 | 'sub-app', 141 | { 142 | name: projectName 143 | }, 144 | nestTree 145 | ); 146 | 147 | const tree = await runner.runSchematic('nest-add', options, nestTree); 148 | const files: string[] = tree.files; 149 | expect(files).toEqual([ 150 | '/.eslintrc.js', 151 | '/.prettierrc', 152 | '/README.md', 153 | '/nest-cli.json', 154 | '/package.json', 155 | '/tsconfig.build.json', 156 | '/tsconfig.json', 157 | '/.funcignore', 158 | '/host.json', 159 | '/local.settings.json', 160 | '/proxies.json', 161 | '/src/app.controller.spec.ts', 162 | '/src/app.controller.ts', 163 | '/src/app.module.ts', 164 | '/src/app.service.ts', 165 | '/src/main.ts', 166 | '/test/app.e2e-spec.ts', 167 | '/test/jest-e2e.json', 168 | '/apps/nestjs-azure-func-http/tsconfig.app.json', 169 | `/apps/${projectName}/tsconfig.app.json`, 170 | `/apps/${projectName}/src/main.ts`, 171 | `/apps/${projectName}/src/${projectName}.controller.spec.ts`, 172 | `/apps/${projectName}/src/${projectName}.controller.ts`, 173 | `/apps/${projectName}/src/${projectName}.module.ts`, 174 | `/apps/${projectName}/src/${projectName}.service.ts`, 175 | `/apps/${projectName}/src/main.azure.ts`, 176 | `/apps/${projectName}/test/jest-e2e.json`, 177 | `/apps/${projectName}/test/app.e2e-spec.ts`, 178 | `/${projectName}/function.json`, 179 | `/${projectName}/index.ts`, 180 | `/${projectName}/sample.dat`, 181 | `/${projectName}/webpack.config.js` 182 | ]); 183 | }); 184 | 185 | it('should have a nest-cli.json for monorepo app', async () => { 186 | const projectName = 'azure-2'; 187 | const options: Schema = { 188 | skipInstall: true, 189 | project: projectName, 190 | sourceRoot: `apps/${projectName}/src` 191 | }; 192 | 193 | await runner.runExternalSchematic( 194 | '@nestjs/schematics', 195 | 'sub-app', 196 | { 197 | name: projectName 198 | }, 199 | nestTree 200 | ); 201 | 202 | const tree = await runner.runSchematic('nest-add', options, nestTree); 203 | const fileContent = getFileContent(tree, '/nest-cli.json'); 204 | const parsedFile = JSON.parse(fileContent); 205 | expect(parsedFile.projects[projectName].sourceRoot).toEqual( 206 | `apps/${projectName}/src` 207 | ); 208 | }); 209 | 210 | it('should import the app.module int main azure file for monorepo app', async () => { 211 | const projectName = 'azure-2'; 212 | const options: Schema = { 213 | skipInstall: true, 214 | project: projectName, 215 | sourceRoot: `apps/${projectName}/src` 216 | }; 217 | 218 | await runner.runExternalSchematic( 219 | '@nestjs/schematics', 220 | 'sub-app', 221 | { 222 | name: projectName 223 | }, 224 | nestTree 225 | ); 226 | 227 | const tree = await runner.runSchematic('nest-add', options, nestTree); 228 | const fileContent = getFileContent( 229 | tree, 230 | `/apps/${projectName}/src/main.azure.ts` 231 | ); 232 | 233 | expect(fileContent).toContain( 234 | `import { AppModule } from './app.module';` 235 | ); 236 | }); 237 | 238 | it('should have the root dir for index file in main azure dir for monorepo app', async () => { 239 | const projectName = 'azure-2'; 240 | const options: Schema = { 241 | skipInstall: true, 242 | project: projectName, 243 | sourceRoot: `apps/${projectName}/src` 244 | }; 245 | 246 | await runner.runExternalSchematic( 247 | '@nestjs/schematics', 248 | 'sub-app', 249 | { 250 | name: projectName 251 | }, 252 | nestTree 253 | ); 254 | 255 | const tree = await runner.runSchematic('nest-add', options, nestTree); 256 | const fileContent = getFileContent(tree, `/${projectName}/index.ts`); 257 | 258 | expect(fileContent).toContain( 259 | `import { createApp } from '../apps/${projectName}/src/main.azure';` 260 | ); 261 | }); 262 | 263 | it('should import the webpack config for monorepo app', async () => { 264 | const projectName = 'azure-2'; 265 | const options: Schema = { 266 | skipInstall: true, 267 | project: projectName, 268 | rootDir: `apps/${projectName}`, 269 | sourceRoot: `apps/${projectName}/src` 270 | }; 271 | 272 | await runner.runExternalSchematic( 273 | '@nestjs/schematics', 274 | 'sub-app', 275 | { 276 | name: projectName 277 | }, 278 | nestTree 279 | ); 280 | const tree = await runner.runSchematic('nest-add', options, nestTree); 281 | 282 | const fileContent = getFileContent( 283 | tree, 284 | `/${projectName}/webpack.config.js` 285 | ); 286 | expect(fileContent).toContain(`filename: '${projectName}/index.js'`); 287 | }); 288 | 289 | it('should add a custom webpack config to the compilerOptions for monorepo app', async () => { 290 | const projectName = 'azure-2'; 291 | const options: Schema = { 292 | skipInstall: true, 293 | project: projectName, 294 | sourceRoot: `apps/${projectName}/src` 295 | }; 296 | 297 | await runner.runExternalSchematic( 298 | '@nestjs/schematics', 299 | 'sub-app', 300 | { 301 | name: projectName 302 | }, 303 | nestTree 304 | ); 305 | const tree = await runner.runSchematic('nest-add', options, nestTree); 306 | 307 | const fileContent = getFileContent(tree, 'nest-cli.json'); 308 | const parsedFile = JSON.parse(fileContent); 309 | const compilerOptions = parsedFile.projects[projectName].compilerOptions; 310 | expect(compilerOptions).toEqual({ 311 | tsConfigPath: `apps/${projectName}/tsconfig.app.json`, 312 | webpack: true, 313 | webpackConfigPath: `${projectName}/webpack.config.js` 314 | }); 315 | }); 316 | 317 | it('should the scriptFile of functions to sub dir for monorepo app', async () => { 318 | const projectName = 'azure-2'; 319 | const options: Schema = { 320 | skipInstall: true, 321 | project: projectName, 322 | rootDir: `apps.${projectName}`, 323 | sourceRoot: `apps/${projectName}/src` 324 | }; 325 | 326 | await runner.runExternalSchematic( 327 | '@nestjs/schematics', 328 | 'sub-app', 329 | { 330 | name: projectName 331 | }, 332 | nestTree 333 | ); 334 | const tree = await runner.runSchematic('nest-add', options, nestTree); 335 | 336 | const fileContent = getFileContent(tree, `${projectName}/function.json`); 337 | const parsedFile = JSON.parse(fileContent); 338 | expect(parsedFile.scriptFile).toEqual(`../dist/${projectName}/index.js`); 339 | }); 340 | }); 341 | 342 | async function createTestNest( 343 | runner: SchematicTestRunner, 344 | tree?: Tree 345 | ): Promise { 346 | return await runner.runExternalSchematic( 347 | '@nestjs/schematics', 348 | 'application', 349 | { 350 | name: 'newproject', 351 | directory: '.' 352 | }, 353 | tree 354 | ); 355 | } 356 | }); 357 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Nest 2 | 3 | We would love for you to contribute to Nest and help make it even better than it is 4 | today! As a contributor, here are the guidelines we would like you to follow: 5 | 6 | - [Code of Conduct](#coc) 7 | - [Question or Problem?](#question) 8 | - [Issues and Bugs](#issue) 9 | - [Feature Requests](#feature) 10 | - [Submission Guidelines](#submit) 11 | - [Coding Rules](#rules) 12 | - [Commit Message Guidelines](#commit) 13 | 14 | 15 | 17 | 18 | ## Got a Question or Problem? 19 | 20 | **Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests.** You've got much better chances of getting your question answered on [Stack Overflow](https://stackoverflow.com/questions/tagged/nestjs) where the questions should be tagged with tag `nestjs`. 21 | 22 | Stack Overflow is a much better place to ask questions since: 23 | 24 | 25 | - questions and answers stay available for public viewing so your question / answer might help someone else 26 | - Stack Overflow's voting system assures that the best answers are prominently visible. 27 | 28 | To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow. 29 | 30 | If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter]. 31 | 32 | ## Found a Bug? 33 | If you find a bug in the source code, you can help us by 34 | [submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can 35 | [submit a Pull Request](#submit-pr) with a fix. 36 | 37 | ## Missing a Feature? 38 | You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub 39 | Repository. If you would like to *implement* a new feature, please submit an issue with 40 | a proposal for your work first, to be sure that we can use it. 41 | Please consider what kind of change it is: 42 | 43 | * For a **Major Feature**, first open an issue and outline your proposal so that it can be 44 | discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, 45 | and help you to craft the change so that it is successfully accepted into the project. For your issue name, please prefix your proposal with `[discussion]`, for example "[discussion]: your feature idea". 46 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 47 | 48 | ## Submission Guidelines 49 | 50 | ### Submitting an Issue 51 | 52 | Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. 53 | 54 | We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using a repository or [Gist](https://gist.github.com/). Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like: 55 | 56 | - version of NestJS used 57 | - 3rd-party libraries and their versions 58 | - and most importantly - a use-case that fails 59 | 60 | 64 | 65 | 66 | 67 | Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced. 68 | 69 | You can file new issues by filling out our [new issue form](https://github.com/nestjs/nest/issues/new). 70 | 71 | 72 | ### Submitting a Pull Request (PR) 73 | Before you submit your Pull Request (PR) consider the following guidelines: 74 | 75 | 1. Search [GitHub](https://github.com/nestjs/nest/pulls) for an open or closed PR 76 | that relates to your submission. You don't want to duplicate effort. 77 | 79 | 1. Fork the nestjs/nest repo. 80 | 1. Make your changes in a new git branch: 81 | 82 | ```shell 83 | git checkout -b my-fix-branch master 84 | ``` 85 | 86 | 1. Create your patch, **including appropriate test cases**. 87 | 1. Follow our [Coding Rules](#rules). 88 | 1. Run the full Nest test suite, as described in the [developer documentation][dev-doc], 89 | and ensure that all tests pass. 90 | 1. Commit your changes using a descriptive commit message that follows our 91 | [commit message conventions](#commit). Adherence to these conventions 92 | is necessary because release notes are automatically generated from these messages. 93 | 94 | ```shell 95 | git commit -a 96 | ``` 97 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 98 | 99 | 1. Push your branch to GitHub: 100 | 101 | ```shell 102 | git push origin my-fix-branch 103 | ``` 104 | 105 | 1. In GitHub, send a pull request to `nestjs:master`. 106 | * If we suggest changes then: 107 | * Make the required updates. 108 | * Re-run the Nest test suites to ensure tests are still passing. 109 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 110 | 111 | ```shell 112 | git rebase master -i 113 | git push -f 114 | ``` 115 | 116 | That's it! Thank you for your contribution! 117 | 118 | #### After your pull request is merged 119 | 120 | After your pull request is merged, you can safely delete your branch and pull the changes 121 | from the main (upstream) repository: 122 | 123 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 124 | 125 | ```shell 126 | git push origin --delete my-fix-branch 127 | ``` 128 | 129 | * Check out the master branch: 130 | 131 | ```shell 132 | git checkout master -f 133 | ``` 134 | 135 | * Delete the local branch: 136 | 137 | ```shell 138 | git branch -D my-fix-branch 139 | ``` 140 | 141 | * Update your master with the latest upstream version: 142 | 143 | ```shell 144 | git pull --ff upstream master 145 | ``` 146 | 147 | ## Coding Rules 148 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 149 | 150 | * All features or bug fixes **must be tested** by one or more specs (unit-tests). 151 | 154 | * We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at 155 | **100 characters**. An automated formatter is available, see 156 | [DEVELOPER.md](docs/DEVELOPER.md#clang-format). 157 | 158 | ## Commit Message Guidelines 159 | 160 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 161 | readable messages** that are easy to follow when looking through the **project history**. But also, 162 | we use the git commit messages to **generate the Nest change log**. 163 | 164 | ### Commit Message Format 165 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 166 | format that includes a **type**, a **scope** and a **subject**: 167 | 168 | ``` 169 | (): 170 | 171 | 172 | 173 |