├── .circleci
└── config.yml
├── .commitlintrc.json
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .release-it.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
├── index.ts
├── jest.json
├── lib
├── adapter
│ ├── azure-adapter.ts
│ ├── azure-reply.ts
│ ├── azure-request.ts
│ └── index.ts
├── azure-http.adapter.ts
├── index.ts
└── router
│ ├── azure-http.router.ts
│ └── index.ts
├── package-lock.json
├── package.json
├── renovate.json
├── schematics
├── collection.json
└── install
│ ├── files
│ ├── project
│ │ ├── .funcignore
│ │ ├── __project__
│ │ │ ├── function.json
│ │ │ ├── index.ts
│ │ │ ├── sample.dat
│ │ │ └── webpack.config.js
│ │ ├── __sourceRoot__
│ │ │ └── main.azure.ts
│ │ ├── host.json
│ │ ├── local.settings.json
│ │ └── proxies.json
│ └── root
│ │ ├── .funcignore
│ │ ├── __rootDir__
│ │ └── main.azure.ts
│ │ ├── host.json
│ │ ├── local.settings.json
│ │ ├── main
│ │ ├── function.json
│ │ ├── index.ts
│ │ └── sample.dat
│ │ └── proxies.json
│ ├── index.test.ts
│ ├── index.ts
│ ├── schema.json
│ └── schema.ts
├── tsconfig.json
└── tsconfig.schematics.json
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | schematics/install/files/**/*.ts
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "singleQuote": true
4 | }
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "commitMessage": "chore(): release v${version}"
4 | },
5 | "github": {
6 | "release": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/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 |
174 | ```
175 |
176 | The **header** is mandatory and the **scope** of the header is optional.
177 |
178 | Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
179 | to read on GitHub as well as in various git tools.
180 |
181 | Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
182 |
183 | Samples: (even more [samples](https://github.com/nestjs/nest/commits/master))
184 |
185 | ```
186 | docs(changelog) update change log to beta.5
187 | ```
188 | ```
189 | fix(@nestjs/core) need to depend on latest rxjs and zone.js
190 |
191 | The version in our package.json gets copied to the one we publish, and users need the latest of these.
192 | ```
193 |
194 | ### Revert
195 | If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit .`, where the hash is the SHA of the commit being reverted.
196 |
197 | ### Type
198 | Must be one of the following:
199 |
200 | * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
201 | * **chore**: Updating tasks etc; no production code change
202 | * **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
203 | * **docs**: Documentation only changes
204 | * **feat**: A new feature
205 | * **fix**: A bug fix
206 | * **perf**: A code change that improves performance
207 | * **refactor**: A code change that neither fixes a bug nor adds a feature
208 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
209 | * **test**: Adding missing tests or correcting existing tests
210 |
211 |
212 | ### Subject
213 | The subject contains succinct description of the change:
214 |
215 | * use the imperative, present tense: "change" not "changed" nor "changes"
216 | * don't capitalize first letter
217 | * no dot (.) at the end
218 |
219 | ### Body
220 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
221 | The body should include the motivation for the change and contrast this with previous behavior.
222 |
223 | ### Footer
224 | The footer should contain any information about **Breaking Changes** and is also the place to
225 | reference GitHub issues that this commit **Closes**.
226 |
227 | **Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
228 |
229 | A detailed explanation can be found in this [document][commit-message-format].
230 |
231 |
239 |
240 |
241 |
242 |
243 | [commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
244 | [corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
245 | [dev-doc]: https://github.com/nestjs/nest/blob/master/docs/DEVELOPER.md
246 | [github]: https://github.com/nestjs/nest
247 | [gitter]: https://gitter.im/nestjs/nest
248 | [individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
249 | [js-style-guide]: https://google.github.io/styleguide/jsguide.html
250 | [jsfiddle]: http://jsfiddle.net
251 | [plunker]: http://plnkr.co/edit
252 | [runnable]: http://runnable.com
253 |
254 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
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 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/adapter/index.ts:
--------------------------------------------------------------------------------
1 | export * from './azure-adapter';
2 | export * from './azure-reply';
3 | export * from './azure-request';
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './azure-http.adapter';
2 | export * from './router';
3 | export * from './adapter';
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/router/index.ts:
--------------------------------------------------------------------------------
1 | export * from './azure-http.router';
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "semanticCommits": true,
3 | "packageRules": [{
4 | "depTypeList": ["devDependencies"],
5 | "automerge": true
6 | }],
7 | "extends": [
8 | "config:base"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/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/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/schematics/install/files/project/__project__/sample.dat:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Azure"
3 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/schematics/install/files/project/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0"
3 | }
4 |
--------------------------------------------------------------------------------
/schematics/install/files/project/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "",
5 | "FUNCTIONS_WORKER_RUNTIME": "node"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/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/__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/root/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0"
3 | }
4 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/schematics/install/files/root/main/sample.dat:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Azure"
3 | }
--------------------------------------------------------------------------------
/schematics/install/files/root/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------