├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── release.yml │ ├── test.yml │ └── validate.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── index.test.js └── lib │ └── utils.js ├── commitlint.config.js ├── docs ├── configure.md ├── output.md └── upload.md ├── example ├── README.md ├── index.html ├── package.json ├── serverless.yml ├── sls.js └── sls.upload.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── release.config.js ├── serverless.component.yml └── src ├── _shims ├── handler.js ├── package.json └── sls.js ├── config.js ├── package.json ├── serverless.js └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_size = 2 11 | indent_style = space 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | example 5 | *.test.js 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['prettier'], 4 | plugins: ['import', 'prettier'], 5 | env: { 6 | es6: true, 7 | jest: true, 8 | node: true 9 | }, 10 | parser: 'babel-eslint', 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: 'module', 14 | ecmaFeatures: { 15 | jsx: true 16 | } 17 | }, 18 | globals: { 19 | on: true // for the Socket file 20 | }, 21 | rules: { 22 | 'array-bracket-spacing': [ 23 | 'error', 24 | 'never', 25 | { 26 | objectsInArrays: false, 27 | arraysInArrays: false 28 | } 29 | ], 30 | 'arrow-parens': ['error', 'always'], 31 | 'arrow-spacing': ['error', { before: true, after: true }], 32 | 'comma-dangle': ['error', 'never'], 33 | curly: 'error', 34 | 'eol-last': 'error', 35 | 'func-names': 'off', 36 | 'id-length': [ 37 | 'error', 38 | { 39 | min: 1, 40 | max: 50, 41 | properties: 'never', 42 | exceptions: ['e', 'i', 'n', 't', 'x', 'y', 'z', '_', '$'] 43 | } 44 | ], 45 | 'no-alert': 'error', 46 | 'no-console': 'off', 47 | 'no-const-assign': 'error', 48 | 'no-else-return': 'error', 49 | 'no-empty': 'off', 50 | 'no-shadow': 'error', 51 | 'no-undef': 'error', 52 | 'no-unused-vars': 'error', 53 | 'no-use-before-define': 'error', 54 | 'no-useless-constructor': 'error', 55 | 'object-curly-newline': 'off', 56 | 'object-shorthand': 'off', 57 | 'prefer-const': 'error', 58 | 'prefer-destructuring': ['error', { object: true, array: false }], 59 | quotes: [ 60 | 'error', 61 | 'single', 62 | { 63 | allowTemplateLiterals: true, 64 | avoidEscape: true 65 | } 66 | ], 67 | semi: ['error', 'never'], 68 | 'spaced-comment': 'error', 69 | strict: ['error', 'global'], 70 | 'prettier/prettier': 'error' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | env: 12 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | with: 17 | persist-credentials: false 18 | 19 | - name: Install Node.js and npm 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 14.x 23 | registry-url: https://registry.npmjs.org 24 | 25 | - name: Retrieve dependencies from cache 26 | id: cacheNpm 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | ~/.npm 31 | node_modules 32 | key: npm-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('package.json') }} 33 | restore-keys: npm-v14-${{ runner.os }}-refs/heads/master- 34 | 35 | - name: Install dependencies 36 | if: steps.cacheNpm.outputs.cache-hit != 'true' 37 | run: | 38 | npm update --no-save 39 | npm update --save-dev --no-save 40 | - name: Releasing 41 | run: | 42 | npm run release 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 45 | GIT_AUTHOR_NAME: slsplus 46 | GIT_AUTHOR_EMAIL: slsplus.sz@gmail.com 47 | GIT_COMMITTER_NAME: slsplus 48 | GIT_COMMITTER_EMAIL: slsplus.sz@gmail.com 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | with: 15 | # Ensure connection with 'master' branch 16 | fetch-depth: 2 17 | 18 | - name: Install Node.js and npm 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 14.x 22 | registry-url: https://registry.npmjs.org 23 | 24 | - name: Retrieve dependencies from cache 25 | id: cacheNpm 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.npm 30 | node_modules 31 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 32 | restore-keys: | 33 | npm-v14-${{ runner.os }}-${{ github.ref }}- 34 | npm-v14-${{ runner.os }}-refs/heads/master- 35 | 36 | - name: Install dependencies 37 | if: steps.cacheNpm.outputs.cache-hit != 'true' 38 | run: | 39 | npm update --no-save 40 | npm update --save-dev --no-save 41 | - name: Running tests 42 | run: npm run test 43 | env: 44 | SERVERLESS_PLATFORM_VENDOR: tencent 45 | GLOBAL_ACCELERATOR_NA: true 46 | TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }} 47 | TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }} 48 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | lintAndFormatting: 9 | name: Lint & Formatting 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | with: 15 | # Ensure connection with 'master' branch 16 | fetch-depth: 2 17 | 18 | - name: Install Node.js and npm 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 14.x 22 | registry-url: https://registry.npmjs.org 23 | 24 | - name: Retrieve dependencies from cache 25 | id: cacheNpm 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.npm 30 | node_modules 31 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 32 | restore-keys: | 33 | npm-v14-${{ runner.os }}-${{ github.ref }}- 34 | npm-v14-${{ runner.os }}-refs/heads/master- 35 | 36 | - name: Install dependencies 37 | if: steps.cacheNpm.outputs.cache-hit != 'true' 38 | run: | 39 | npm update --no-save 40 | npm update --save-dev --no-save 41 | 42 | - name: Validate Formatting 43 | run: npm run prettier:fix 44 | - name: Validate Lint rules 45 | run: npm run lint:fix 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | *.log 5 | .serverless 6 | v8-compile-cache-* 7 | jest/* 8 | coverage 9 | .serverless_plugins 10 | testProjects/*/package-lock.json 11 | testProjects/*/yarn.lock 12 | .serverlessUnzipped 13 | node_modules 14 | .vscode/ 15 | .eslintcache 16 | dist 17 | .idea 18 | build/ 19 | .env* 20 | env.js 21 | package-lock.json 22 | test 23 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | example -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | CHANGELOG.md 5 | *.test.js 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.3.0](https://github.com/serverless-components/tencent-express/compare/v0.2.3...v0.3.0) (2021-01-26) 2 | 3 | 4 | ### Features 5 | 6 | * support apigw base64 encode ([78ca266](https://github.com/serverless-components/tencent-express/commit/78ca26613eec65558852ff1b6a130a841b0178f3)) 7 | 8 | ## [0.2.3](https://github.com/serverless-components/tencent-express/compare/v0.2.2...v0.2.3) (2020-12-28) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * add sls initialize before creating server ([7f18f2c](https://github.com/serverless-components/tencent-express/commit/7f18f2c5e8e86cc0a9252e5bd036742251964e4d)) 14 | 15 | ## [0.2.2](https://github.com/serverless-components/tencent-express/compare/v0.2.1...v0.2.2) (2020-12-15) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * update remove flow ([f766ee6](https://github.com/serverless-components/tencent-express/commit/f766ee69cf39cf410df0203fc71644297ef70693)) 21 | 22 | ## [0.2.1](https://github.com/serverless-components/tencent-express/compare/v0.2.0...v0.2.1) (2020-12-15) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * update to deployment to serial flow ([a76fe58](https://github.com/serverless-components/tencent-express/commit/a76fe586846269132f3a9b9141302050b02a13c3)) 28 | 29 | # [0.2.0](https://github.com/serverless-components/tencent-express/compare/v0.1.5...v0.2.0) (2020-10-12) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * support all parameters for apigw ([ec3ffc7](https://github.com/serverless-components/tencent-express/commit/ec3ffc7fb6a6959dbdb734b8a2137f5ac777d05c)) 35 | 36 | 37 | ### Features 38 | 39 | * support custom entry file and all scf config ([80e2890](https://github.com/serverless-components/tencent-express/commit/80e28903422c5710a56a8267f64ffe47398ddcf8)) 40 | 41 | ## [0.1.5](https://github.com/serverless-components/tencent-express/compare/v0.1.4...v0.1.5) (2020-09-03) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * update deploy flow for multi region ([33ae212](https://github.com/serverless-components/tencent-express/commit/33ae21248a7c191e3ceea2371fd2e69a6b1ebf67)) 47 | 48 | ## [0.1.4](https://github.com/serverless-components/tencent-express/compare/v0.1.3...v0.1.4) (2020-09-02) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * update tencnet-component-toolkit for api mark ([7e25e8a](https://github.com/serverless-components/tencent-express/commit/7e25e8a86f539b2db449caf969cbbd58c5af4d1b)) 54 | 55 | ## [0.1.3](https://github.com/serverless-components/tencent-express/compare/v0.1.2...v0.1.3) (2020-09-01) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * support cfs ([9d1a1e1](https://github.com/serverless-components/tencent-express/commit/9d1a1e1793d866f5fbe5bdce61090db8381bb103)) 61 | * update deps for error message ([f0a8f89](https://github.com/serverless-components/tencent-express/commit/f0a8f892f469efcda67070ff940421aa83a100c0)) 62 | 63 | ## [0.1.2](https://github.com/serverless-components/tencent-express/compare/v0.1.1...v0.1.2) (2020-08-26) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * add apigw/scf deployed state saving manually ([0fcc0b8](https://github.com/serverless-components/tencent-express/commit/0fcc0b84bdeb7119086cdbb050ae2c8ab734ff32)) 69 | * add regionList state ([f66d8d8](https://github.com/serverless-components/tencent-express/commit/f66d8d8ee6564e343442bb59b9996bf23fd475b9)) 70 | * add release config ([26c7e12](https://github.com/serverless-components/tencent-express/commit/26c7e1201caf79830843e860237f3ebf6efdf1e7)) 71 | * apigw custom domain update bug ([1a53027](https://github.com/serverless-components/tencent-express/commit/1a53027effde871dc9f021f510fd7a13cc86794a)) 72 | * apigw isDisabled ([f624171](https://github.com/serverless-components/tencent-express/commit/f624171b927f0efd4605566c6168d67a3f9b8302)) 73 | * bind unexist role bug ([6cd8990](https://github.com/serverless-components/tencent-express/commit/6cd89900111c7afd1e073d95f81f99364debfb96)) 74 | * cache http server ([fc5c824](https://github.com/serverless-components/tencent-express/commit/fc5c82453e2469d66837e1b525d317e811b3115a)) 75 | * change entry to sls.js ([61bd482](https://github.com/serverless-components/tencent-express/commit/61bd4820dfcacefa1338580fa8d3b0f2609ffe4c)) 76 | * cns ([65aa8b8](https://github.com/serverless-components/tencent-express/commit/65aa8b8b42880c9709ff583429899275978b007e)) 77 | * enableCORS for apigw ([a0f59ef](https://github.com/serverless-components/tencent-express/commit/a0f59ef01d5dfe500758adfe2c4f05d7ffc2f22b)) 78 | * handle usageplan & auth undefined ([2a257d5](https://github.com/serverless-components/tencent-express/commit/2a257d564e0968c8db1e0a4030412749cb19e847)) 79 | * make default runtime to 10.15 ([a295bd4](https://github.com/serverless-components/tencent-express/commit/a295bd49a9b48f470a3416026531e840dd2177ac)) 80 | * metics data bug ([6bdf89b](https://github.com/serverless-components/tencent-express/commit/6bdf89bbe3b3665012acee5ca66e08fdff9f05db)) 81 | * metrics qps limit ([dddb9f6](https://github.com/serverless-components/tencent-express/commit/dddb9f607c96d86a3500eddeae248d600e2c9813)) 82 | * monitor timeout bug ([de33311](https://github.com/serverless-components/tencent-express/commit/de333111a692b5bc8d14e9274b81b5b2e5e57e84)) 83 | * optimize outputs for one region ([b3c6964](https://github.com/serverless-components/tencent-express/commit/b3c69646528edfdb8ff0d6455a4dc2b16e13cb81)) 84 | * optimize traffic config outputs ([8545ead](https://github.com/serverless-components/tencent-express/commit/8545ead58183454536ca43c412d42d9be9c05580)) 85 | * package version ([d682b66](https://github.com/serverless-components/tencent-express/commit/d682b66ab7858bb85c29548f90514056f8c34749)) 86 | * prettier config ([e17bd6f](https://github.com/serverless-components/tencent-express/commit/e17bd6fe939666f7d8c5c89182ed24125409e068)) 87 | * read bucket and object from srcOriginal ([1b2cf9d](https://github.com/serverless-components/tencent-express/commit/1b2cf9d7c890a94e7deaa830814b9758ac42cdf4)) 88 | * release v0.0.1 for v2 ([52b2a37](https://github.com/serverless-components/tencent-express/commit/52b2a37a9f57550d48af868b6d53ac3a9c020fd5)) 89 | * remove credential bug ([bca2d1f](https://github.com/serverless-components/tencent-express/commit/bca2d1f9c6ab79ee7b29844fe8bdf2085ddac94c)) 90 | * role check error ([1a0b4bf](https://github.com/serverless-components/tencent-express/commit/1a0b4bf3ec1fed5492d72f07378ca7b5d26bbfe8)) 91 | * support apigw endpoint timeout config ([01dd4c9](https://github.com/serverless-components/tencent-express/commit/01dd4c931235ec5547ce5f34e0997beb4e094fd1)) 92 | * support eip config ([62c597f](https://github.com/serverless-components/tencent-express/commit/62c597fdef0a8e296b1943dba13baa60725afd53)) 93 | * template url output ([88df485](https://github.com/serverless-components/tencent-express/commit/88df48503bd1f106e451ce102ec5bd16d6a1442a)) 94 | * throw error when no temp secrets ([2a9167e](https://github.com/serverless-components/tencent-express/commit/2a9167e98a9648fb3ed7264956b10ea2808ce490)) 95 | * traffic zero display bug ([0285406](https://github.com/serverless-components/tencent-express/commit/0285406c812284bd8b5847f0c201c9b69761c9d6)) 96 | * uniform throw error ([c09b6f5](https://github.com/serverless-components/tencent-express/commit/c09b6f5b0071a2fb6a3251b0f8f31bc0dd614d3b)) 97 | * update deploy code ([90a4339](https://github.com/serverless-components/tencent-express/commit/90a433968502f9d1499534cf852c6d4c3db12ba6)) 98 | * update deps ([2e551db](https://github.com/serverless-components/tencent-express/commit/2e551db26dcfa02adf5aa2c63d6c08fea771ed00)) 99 | * update deps ([c14e8f1](https://github.com/serverless-components/tencent-express/commit/c14e8f1492fa8ca7e3bedbc8018571556aa0c6ff)) 100 | * update error message ([c4c7243](https://github.com/serverless-components/tencent-express/commit/c4c724358fe5be700e596929b8bddcfa94faa6fd)) 101 | * update get credential error message ([ed47b3d](https://github.com/serverless-components/tencent-express/commit/ed47b3d759944e41b0c8fa7641594450d88704de)) 102 | * update toolkit verison ([2e99bc7](https://github.com/serverless-components/tencent-express/commit/2e99bc73c438318d125d50d959bf3f9de4a6e85e)) 103 | * update usageplan & auth logic ([afa8807](https://github.com/serverless-components/tencent-express/commit/afa8807792aa0e69c191391b1fb49b059add3c35)) 104 | * upgrade deps ([6a1f7a8](https://github.com/serverless-components/tencent-express/commit/6a1f7a86d4838d02cef19de37a2f6e9714ee640b)) 105 | * upgrade deps ([d5c013b](https://github.com/serverless-components/tencent-express/commit/d5c013b6a04487a29b9848442d31cc71c6230c5f)) 106 | * upgrade tencent-component-toolkit ([4ba2edf](https://github.com/serverless-components/tencent-express/commit/4ba2edf8570e511b9fd5a8286ae81044b531c094)) 107 | * upgrade tencent-component-toolkit ([af8c234](https://github.com/serverless-components/tencent-express/commit/af8c234622058253a21ebe0231796dd7a47902f8)) 108 | * upgrade tencent-component-toolkit ([bdd5062](https://github.com/serverless-components/tencent-express/commit/bdd5062f6501fbe91e167ce3e7a91da2e8eb4acd)) 109 | * upgrade tencent-component-toolkit ([00f4a09](https://github.com/serverless-components/tencent-express/commit/00f4a0993d04d4a7be3cf8ea15381a6d8ff04c98)) 110 | * upgrade tencent-component-toolkit for deleting compatibility ([fa215bf](https://github.com/serverless-components/tencent-express/commit/fa215bfb66264c360c24a3aca68f4b63cb4c60fd)) 111 | * wrong region parameter bug ([#31](https://github.com/serverless-components/tencent-express/issues/31)) ([32e519c](https://github.com/serverless-components/tencent-express/commit/32e519ca00170ed75f0bea19b80c0255288e8d37)) 112 | * 增加cns ([b452285](https://github.com/serverless-components/tencent-express/commit/b45228501a381dd6ede9381ca8d05cd5f403d585)) 113 | * 增加cns ([a2b87d9](https://github.com/serverless-components/tencent-express/commit/a2b87d92908935dc4299dfb0d5b01029c5fc8649)) 114 | 115 | 116 | ### Features 117 | 118 | * add binary types support ([60603d6](https://github.com/serverless-components/tencent-express/commit/60603d61bbeb1cbdcf44d5bb6b9cd5a6f2b08fd6)) 119 | * add integration test ([7c28271](https://github.com/serverless-components/tencent-express/commit/7c2827162f94b817f5e8f3c00e279263bff120e7)) 120 | * add layers config support ([d971be9](https://github.com/serverless-components/tencent-express/commit/d971be985daf3bc4127c6648fd0e474c535ad1eb)) 121 | * add metrics api ([#27](https://github.com/serverless-components/tencent-express/issues/27)) ([0d40154](https://github.com/serverless-components/tencent-express/commit/0d40154347b5db735fcac66a8a9b5ccf0e6ca035)) 122 | * add state store, and remove method ([8815e40](https://github.com/serverless-components/tencent-express/commit/8815e4045cf7896e8843c017abb16b24720caa79)) 123 | * optimize code zip flow ([f2d60ce](https://github.com/serverless-components/tencent-express/commit/f2d60ce03e96257f4aacda464ab83a4dc144fecb)) 124 | * optimize deploy log ([80a2bc0](https://github.com/serverless-components/tencent-express/commit/80a2bc0a23a08051ede3c5d54657833499e0d2fc)) 125 | * optimize metics and support disable apigw creating ([e722c80](https://github.com/serverless-components/tencent-express/commit/e722c80d3d1fce8315e08a86808c131402a7a81b)) 126 | * support api gw metrics ([cc625ad](https://github.com/serverless-components/tencent-express/commit/cc625ad3e8a410b161b4744813685aa42a700898)) 127 | * support role config ([f1d6ed6](https://github.com/serverless-components/tencent-express/commit/f1d6ed664b7354376f5791bd162113cdd44b7b5b)) 128 | * support scf publish version and traffic setup ([8807d0e](https://github.com/serverless-components/tencent-express/commit/8807d0ef0fd8c560b3c7aaa8fa37165473a5f863)) 129 | * update config and support usageplan+auth ([b067a14](https://github.com/serverless-components/tencent-express/commit/b067a149acec78d97f701aea893e95ad3833f7c3)) 130 | * update event and context attach method ([31d0c1d](https://github.com/serverless-components/tencent-express/commit/31d0c1de42354103a800e0515f9a069971b357df)) 131 | * use metrics api from toolkit library([#32](https://github.com/serverless-components/tencent-express/issues/32)) ([4b4742d](https://github.com/serverless-components/tencent-express/commit/4b4742d7e53a04fbbd7845073a0ebd2031a1dcd6)) 132 | * using temporary secret for credentials ([cf61300](https://github.com/serverless-components/tencent-express/commit/cf61300f7d94f3149648a0d3378b8876a440aca1)) 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tencent Cloud, Inc. 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 | ⚠️⚠️⚠️ 所有框架组件项目迁移到 [tencent-framework-components](https://github.com/serverless-components/tencent-framework-components). 2 | 3 | [![Serverless Components](https://img.serverlesscloud.cn/2020210/1581352135771-express.png)](http://serverless.com) 4 | 5 |
6 | 7 | **腾讯云 Express 组件** ⎯⎯⎯ 通过使用 [Tencent Serverless Framework](https://github.com/serverless/components/tree/cloud),基于云上 Serverless 服务(如网关、云函数等),实现“0”配置,便捷开发,极速部署你的 Express 应用,Express 组件支持丰富的配置扩展,提供了目前最易用、低成本并且弹性伸缩的 Express 项目开发/托管能力。 8 |
9 | 10 | 特性介绍: 11 | 12 | - [x] **按需付费** - 按照请求的使用量进行收费,没有请求时无需付费 13 | - [x] **"0"配置** - 只需要关心项目代码,之后部署即可,Serverless Framework 会搞定所有配置。 14 | - [x] **极速部署** - 仅需几秒,部署你的整个 Express 应用。 15 | - [x] **实时日志** - 通过实时日志的输出查看业务状态,便于直接在云端开发应用。 16 | - [x] **云端调试** - 针对 Node.js 框架支持一键云端调试能力,屏蔽本地环境的差异。 17 | - [x] **便捷协作** - 通过云端的状态信息和部署日志,方便的进行多人协作开发。 18 | - [x] **自定义域名** - 支持配置自定义域名及 HTTPS 访问 19 | 20 |
21 | 22 | 快速开始: 23 | 24 | 1. [**安装**](#1-安装) 25 | 2. [**创建**](#2-创建) 26 | 3. [**部署**](#3-部署) 27 | 4. [**配置**](#4-配置) 28 | 5. [**开发调试**](#5-开发调试) 29 | 6. [**查看状态**](#6-查看状态) 30 | 7. [**移除**](#7-移除) 31 | 32 | 更多资源: 33 | 34 | - [**架构说明**](#架构说明) 35 | - [**账号配置**](#账号配置) 36 | 37 |   38 | 39 | ### 1. 安装 40 | 41 | 通过 npm 安装最新版本的 Serverless Framework 42 | 43 | ```bash 44 | $ npm install -g serverless 45 | ``` 46 | 47 | ### 2. 创建 48 | 49 | 通过如下命令和模板链接,快速创建一个 Express 应用: 50 | 51 | ```bash 52 | $ serverless init express-starter --name example 53 | $ cd example 54 | ``` 55 | 56 | ### 3. 部署 57 | 58 | 在 `serverless.yml` 文件所在的项目根目录,运行以下指令进行部署: 59 | 60 | ```bash 61 | $ serverless deploy 62 | ``` 63 | 64 | 部署时需要进行身份验证,如您的账号未 [登陆](https://cloud.tencent.com/login) 或 [注册](https://cloud.tencent.com/register) 腾讯云,您可以直接通过 `微信` 扫描命令行中的二维码进行授权登陆和注册。 65 | 66 | > 注意: 如果希望查看更多部署过程的信息,可以通过`serverless deploy --debug` 命令查看部署过程中的实时日志信息。 67 | 68 | 部署完成后,控制台会打印相关的输出信息,您可以通过 `${output:${stage}:${app}:apigw.url}` 的形式在其他 `serverless` 组件中引用该组件的 API 网关访问链接(或通过类似的形式引用该组建其他输出结果),具体的,可以查看完成的输出文档: 69 | 70 | - [点击此处查看输出文档](https://github.com/serverless-components/tencent-express/tree/master/docs/output.md) 71 | 72 | ### 4. 配置 73 | 74 | Express 组件支持 0 配置部署,也就是可以直接通过配置文件中的默认值进行部署。但你依然可以修改更多可选配置来进一步开发该 Express 项目。 75 | 76 | 以下是 Express 组件的 `serverless.yml`配置示例: 77 | 78 | ```yml 79 | # serverless.yml 80 | 81 | component: express # (required) name of the component. In that case, it's express. 82 | name: expressDemo # (required) name of your express component instance. 83 | org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup. 84 | app: appDemo # (optional) serverless dashboard app. default is the same as the name property. 85 | stage: dev # (optional) serverless dashboard stage. default is dev. 86 | 87 | inputs: 88 | src: 89 | src: ./ # (optional) path to the source folder. default is a hello world app. 90 | exclude: 91 | - .env 92 | functionName: expressDemo 93 | region: ap-guangzhou 94 | runtime: Nodejs10.15 95 | apigatewayConf: 96 | protocols: 97 | - http 98 | - https 99 | environment: release 100 | ``` 101 | 102 | 点此查看[全量配置及配置说明](https://github.com/serverless-components/tencent-express/tree/master/docs/configure.md) 103 | 104 | 当你根据该配置文件更新配置字段后,再次运行 `serverless deploy` 或者 `serverless` 就可以更新配置到云端。 105 | 106 | ### 5. 开发调试 107 | 108 | 部署了 Express.js 应用后,可以通过开发调试能力对该项目进行二次开发,从而开发一个生产应用。在本地修改和更新代码后,不需要每次都运行 `serverless deploy` 命令来反复部署。你可以直接通过 `serverless dev` 命令对本地代码的改动进行检测和自动上传。 109 | 110 | 可以通过在 `serverless.yml`文件所在的目录下运行 `serverless dev` 命令开启开发调试能力。 111 | 112 | `serverless dev` 同时支持实时输出云端日志,每次部署完毕后,对项目进行访问,即可在命令行中实时输出调用日志,便于查看业务情况和排障。 113 | 114 | 除了实时日志输出之外,针对 Node.js 应用,当前也支持云端调试能力。在开启 `serverless dev` 命令之后,将会自动监听远端端口,并将函数的超时时间临时配置为 900s。此时你可以通过访问 chrome://inspect/#devices 查找远端的调试路径,并直接对云端代码进行断点等调试。在调试模式结束后,需要再次部署从而将代码更新并将超时时间设置为原来的值。详情参考[开发模式和云端调试](https://cloud.tencent.com/document/product/1154/43220)。 115 | 116 | ### 6. 查看状态 117 | 118 | 在`serverless.yml`文件所在的目录下,通过如下命令查看部署状态: 119 | 120 | ``` 121 | $ serverless info 122 | ``` 123 | 124 | ### 7. 移除 125 | 126 | 在`serverless.yml`文件所在的目录下,通过以下命令移除部署的 Express 服务。移除后该组件会对应删除云上部署时所创建的所有相关资源。 127 | 128 | ``` 129 | $ serverless remove 130 | ``` 131 | 132 | 和部署类似,支持通过 `serverless remove --debug` 命令查看移除过程中的实时日志信息。 133 | 134 | ## 架构说明 135 | 136 | Express 组件将在腾讯云账户中使用到如下 Serverless 服务: 137 | 138 | - [x] **API 网关** - API 网关将会接收外部请求并且转发到 SCF 云函数中。 139 | - [x] **SCF 云函数** - 云函数将承载 Express.js 应用。 140 | - [x] **CAM 访问控制** - 该组件会创建默认 CAM 角色用于授权访问关联资源。 141 | - [x] **COS 对象存储** - 为确保上传速度和质量,云函数压缩并上传代码时,会默认将代码包存储在特定命名的 COS 桶中。 142 | - [x] **SSL 证书服务** - 如果你在 yaml 文件中配置了 `apigatewayConf.customDomains` 字段,需要做自定义域名绑定并开启 HTTPS 时,也会用到证书管理服务和域名服务。Serverless Framework 会根据已经备案的域名自动申请并配置 SSL 证书。 143 | 144 | ## 账号配置 145 | 146 | 当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件 147 | 148 | ```console 149 | $ touch .env # 腾讯云的配置信息 150 | ``` 151 | 152 | 在 `.env` 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存 153 | 154 | 如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。 155 | 156 | 如果已有腾讯云账号,可以在[API 密钥管理](https://console.cloud.tencent.com/cam/capi)中获取 `SecretId` 和`SecretKey`. 157 | 158 | ``` 159 | # .env 160 | TENCENT_SECRET_ID=123 161 | TENCENT_SECRET_KEY=123 162 | ``` 163 | 164 | ## 静态资源服务 165 | 166 | 如果想要支持返回静态资源,比如图片之类的,需要在入口文件 `sls.js` 中指定相关 `MIME` 类型的文件为二进制,这样云函数在返回请求结果给 API 网关是,会对指定类型进行 `Base64` 编码,最终返回给客户端才能正常显示。如下: 167 | 168 | ```js 169 | const express = require('express') 170 | const app = express() 171 | 172 | // Routes 173 | // ... 174 | 175 | app.binaryTypes = ['*/*'] 176 | 177 | module.exports = app 178 | ``` 179 | 180 | `['*/*']` 代表所有文件类型将进行 `Base64` 编码,如果需要定制化,可以配置为 `['image/png']`,意思是指定 `png` 格式的图片类型。 181 | 182 | 更多文件类型的 `MIME` 类型,可参考 [mime-db](https://github.com/jshttp/mime-db/blob/master/db.json)。 183 | 184 | ### slsInitialize 应用初始化 185 | 186 | 有些时候,Express 服务在启动前,需要进行一个初始化操作,比如数据库建连,就可以通过在 Express 实例对象上添加 `slsInitialize` 函数来实现,如下: 187 | 188 | ```js 189 | const express = require('express') 190 | const mysql = require('mysql2/promise') 191 | 192 | const app = new express() 193 | 194 | // ... 195 | 196 | app.slsInitialize = async () => { 197 | app.db = await mysql.createConnection({ 198 | host: 'localhost', 199 | user: 'root', 200 | database: 'test' 201 | }) 202 | } 203 | 204 | // don't forget to export! 205 | module.exports = app 206 | ``` 207 | 208 | 这样应用部署到云函数后,在函数服务逻辑执行前,会先执行 `slsInitialize()` 函数,来初始化数据库连接。 209 | 210 | ## License 211 | 212 | MIT License 213 | 214 | Copyright (c) 2020 Tencent Cloud, Inc. 215 | -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | require('dotenv').config({ path: join(__dirname, '.env.test') }); 3 | 4 | const { generateId, getServerlessSdk } = require('./lib/utils') 5 | const execSync = require('child_process').execSync 6 | const path = require('path') 7 | const axios = require('axios') 8 | 9 | const instanceYaml = { 10 | org: 'orgDemo', 11 | app: 'appDemo', 12 | component: 'express@dev', 13 | name: `express-integration-tests-${generateId()}`, 14 | stage: 'dev', 15 | inputs: { 16 | region: 'ap-guangzhou', 17 | runtime: 'Nodejs10.15', 18 | apigatewayConf: { environment: 'test' } 19 | } 20 | } 21 | 22 | const credentials = { 23 | tencent: { 24 | SecretId: process.env.TENCENT_SECRET_ID, 25 | SecretKey: process.env.TENCENT_SECRET_KEY, 26 | } 27 | } 28 | 29 | const sdk = getServerlessSdk(instanceYaml.org) 30 | 31 | it('should successfully deploy express app', async () => { 32 | const instance = await sdk.deploy(instanceYaml, credentials) 33 | 34 | expect(instance).toBeDefined() 35 | expect(instance.instanceName).toEqual(instanceYaml.name) 36 | expect(instance.outputs.templateUrl).toBeDefined() 37 | expect(instance.outputs.region).toEqual(instanceYaml.inputs.region) 38 | expect(instance.outputs.apigw).toBeDefined() 39 | expect(instance.outputs.apigw.environment).toEqual(instanceYaml.inputs.apigatewayConf.environment) 40 | expect(instance.outputs.scf).toBeDefined() 41 | expect(instance.outputs.scf.runtime).toEqual(instanceYaml.inputs.runtime) 42 | }) 43 | 44 | it('should successfully update source code', async () => { 45 | // change source to own source './src' and need to install packages before deploy 46 | const srcPath = path.join(__dirname, '..', 'example') 47 | execSync('npm install', { cwd: srcPath }) 48 | instanceYaml.inputs.src = srcPath 49 | 50 | const instance = await sdk.deploy(instanceYaml, credentials) 51 | const response = await axios.get(instance.outputs.apigw.url) 52 | 53 | expect(response.data.includes('Serverless Framework')).toBeTruthy() 54 | expect(instance.outputs.templateUrl).not.toBeDefined() 55 | }) 56 | 57 | it('should successfully remove express app', async () => { 58 | await sdk.remove(instanceYaml, credentials) 59 | result = await sdk.getInstance(instanceYaml.org, instanceYaml.stage, instanceYaml.app, instanceYaml.name) 60 | 61 | expect(result.instance.instanceStatus).toEqual('inactive') 62 | }) 63 | -------------------------------------------------------------------------------- /__tests__/lib/utils.js: -------------------------------------------------------------------------------- 1 | const { ServerlessSDK } = require('@serverless/platform-client-china') 2 | 3 | /* 4 | * Generate random id 5 | */ 6 | const generateId = () => 7 | Math.random() 8 | .toString(36) 9 | .substring(6) 10 | 11 | /* 12 | * Initializes and returns an instance of the serverless sdk 13 | * @param ${string} orgName - the serverless org name. 14 | */ 15 | const getServerlessSdk = (orgName) => { 16 | const sdk = new ServerlessSDK({ 17 | context: { 18 | orgName 19 | } 20 | }) 21 | return sdk 22 | } 23 | 24 | module.exports = { generateId, getServerlessSdk } 25 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const Configuration = { 2 | /* 3 | * Resolve and load @commitlint/config-conventional from node_modules. 4 | * Referenced packages must be installed 5 | */ 6 | extends: ['@commitlint/config-conventional'] 7 | } 8 | 9 | module.exports = Configuration 10 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | # 配置文档 2 | 3 | ## 全部配置 4 | 5 | ```yml 6 | # serverless.yml 7 | 8 | component: express # (必选) 组件名称,在该实例中为express 9 | name: expressDemo # 必选) 组件实例名称. 10 | org: orgDemo # (可选) 用于记录组织信息,默认值为您的腾讯云账户 appid,必须为字符串 11 | app: appDemo # (可选) 用于记录组织信息. 默认与name相同,必须为字符串 12 | stage: dev # (可选) 用于区分环境信息,默认值是 dev 13 | 14 | inputs: 15 | region: ap-guangzhou # 云函数所在区域 16 | functionName: expressDemo # 云函数名称 17 | serviceName: mytest # api网关服务名称 18 | runtime: Nodejs10.15 # 运行环境 19 | serviceId: service-np1uloxw # api网关服务ID 20 | entryFile: sls.js # 自定义 server 的入口文件名,默认为 sls.js,如果不想修改文件名为 sls.js 可以自定义 21 | src: ./src # 第一种为string时,会打包src对应目录下的代码上传到默认cos上。 22 | # src: # 第二种,部署src下的文件代码,并打包成zip上传到bucket上 23 | # src: ./src # 本地需要打包的文件目录 24 | # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid 25 | # exclude: # 被排除的文件或目录 26 | # - .env 27 | # - node_modules 28 | # src: # 第三种,在指定存储桶bucket中已经存在了object代码,直接部署 29 | # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid 30 | # object: cos.zip # bucket key 指定存储桶内的文件 31 | layers: 32 | - name: layerName # layer名称 33 | version: 1 # 版本 34 | functionConf: # 函数配置相关 35 | timeout: 10 # 超时时间,单位秒 36 | eip: false # 是否固定出口IP 37 | memorySize: 128 # 内存大小,单位MB 38 | environment: # 环境变量 39 | variables: # 环境变量数组 40 | TEST: vale 41 | vpcConfig: # 私有网络配置 42 | vpcId: '' # 私有网络的Id 43 | subnetId: '' # 子网ID 44 | apigatewayConf: # api网关配置 45 | isDisabled: false # 是否禁用自动创建 API 网关功能 46 | enableCORS: true # 允许跨域 47 | customDomains: # 自定义域名绑定 48 | - domain: abc.com # 待绑定的自定义的域名 49 | certificateId: abcdefg # 待绑定自定义域名的证书唯一 ID 50 | # 如要设置自定义路径映射,请设置为 false 51 | isDefaultMapping: false 52 | # 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 53 | pathMappingSet: 54 | - path: / 55 | environment: release 56 | protocols: # 绑定自定义域名的协议类型,默认与服务的前端协议一致。 57 | - http # 支持http协议 58 | - https # 支持https协议 59 | protocols: 60 | - http 61 | - https 62 | environment: test 63 | serviceTimeout: 15 64 | isBase64Encoded: false 65 | usagePlan: # 用户使用计划 66 | usagePlanId: 1111 67 | usagePlanName: slscmp 68 | usagePlanDesc: sls create 69 | maxRequestNum: 1000 70 | auth: # 密钥 71 | secretName: secret 72 | secretIds: 73 | - xxx 74 | ``` 75 | 76 | ## 配置描述 77 | 78 | 主要的参数 79 | 80 | | 参数名称 | 必选 | 默认值 | 描述 | 81 | | ------------------------------------ | :--: | :-------------: | :------------------------------------------------------------------ | 82 | | runtime | 否 | `Nodejs10.15` | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16 | 83 | | region | 否 | `ap-guangzhou` | 项目部署所在区域,默认广州区 | 84 | | functionName | 否 | | 云函数名称 | 85 | | serviceName | 否 | | API 网关服务名称, 默认创建一个新的服务名称 | 86 | | serviceId | 否 | | API 网关服务 ID,如果存在将使用这个 API 网关服务 | 87 | | entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | 88 | | src | 否 | `process.cwd()` | 默认为当前目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | 89 | | layers | 否 | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) | 90 | | [functionConf](#函数配置) | 否 | | 函数配置 | 91 | | [apigatewayConf](#API-网关配置) | 否 | | API 网关配置 | 92 | | [cloudDNSConf](#DNS-配置) | 否 | | DNS 配置 | 93 | | [Region special config](#指定区配置) | 否 | | 指定区配置 | 94 | 95 | ## 执行目录 96 | 97 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 98 | | -------- | :------: | :-------------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 99 | | src | 否 | String | | 代码路径。与 object 不能同时存在。 | 100 | | exclude | 否 | Array of String | | 不包含的文件或路径, 遵守 [glob 语法](https://github.com/isaacs/node-glob) | 101 | | bucket | 否 | String | | bucket 名称。如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 object,表示获取 bucket-appid 对应存储桶中 object 对应的代码进行部署。 | 102 | | object | 否 | String | | 部署的代码在存储桶中的路径。 | 103 | 104 | ## 层配置 105 | 106 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 107 | | -------- | :------: | :----: | :----: | :------- | 108 | | name | 否 | String | | 层名称 | 109 | | version | 否 | String | | 层版本号 | 110 | 111 | ### DNS 配置 112 | 113 | 参考: https://cloud.tencent.com/document/product/302/8516 114 | 115 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 116 | | ---------- | :------: | -------- | :----: | :---------------------------------------------- | 117 | | ttl | 否 | Number | `600` | TTL 值,范围 1 - 604800,不同等级域名最小值不同 | 118 | | recordLine | 否 | String[] | | 记录的线路名称 | 119 | 120 | ### 指定区配置 121 | 122 | | 参数名称 | 是否必选 | 类型 | 默认值 | 函数 | 123 | | ------------------------------- | :------: | ------ | ------ | ------------ | 124 | | [functionConf](#函数配置) | 否 | Object | | 函数配置 | 125 | | [apigatewayConf](#API-网关配置) | 否 | Object | | API 网关配置 | 126 | | [cloudDNSConf](#DNS-配置) | 否 | Object | | DNS 配置 | 127 | 128 | ### 函数配置 129 | 130 | 参考: https://cloud.tencent.com/document/product/583/18586 131 | 132 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 133 | | ----------- | :------: | :-----: | :-----: | :------------------------------------------------------------------------------ | 134 | | timeout | 否 | Number | `3` | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 | 135 | | memorySize | 否 | Number | `128` | 函数运行时内存大小,默认为 128M,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 | 136 | | environment | 否 | Object | | 函数的环境变量, 参考 [环境变量](#环境变量) | 137 | | vpcConfig | 否 | Object | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) | 138 | | eip | 否 | Boolean | `false` | 是否固定出口 IP | 139 | 140 | ##### 环境变量 141 | 142 | | 参数名称 | 类型 | 描述 | 143 | | --------- | ---- | :---------------------------------------- | 144 | | variables | | 环境变量参数, 包含多对 key-value 的键值对 | 145 | 146 | ##### VPC 配置 147 | 148 | | 参数名称 | 类型 | 描述 | 149 | | -------- | ------ | :------ | 150 | | subnetId | String | 子网 ID | 151 | | vpcId | String | VPC ID | 152 | 153 | ### API 网关配置 154 | 155 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 156 | | --------------- | :------: | :------- | :--------- | :--------------------------------------------------------------------------------- | 157 | | protocols | 否 | String[] | `['http']` | 前端请求的类型,如 http,https,http 与 https | 158 | | environment | 否 | String | `release` | 发布环境. 目前支持三种发布环境: test(测试), prepub(预发布) 与 release(发布). | 159 | | usagePlan | 否 | | | 使用计划配置, 参考 [使用计划](#使用计划) | 160 | | auth | 否 | | | API 密钥配置, 参考 [API 密钥](#API-密钥配置) | 161 | | customDomain | 否 | Object[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) | 162 | | enableCORS | 否 | Boolean | `false` | 开启跨域。默认值为否。 | 163 | | serviceTimeout | 否 | Number | `15` | Api 超时时间,单位: 秒 | 164 | | isDisabled | 否 | Boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 | 165 | | isBase64Encoded | 否 | Boolean | `false` | 是否开启 Base64 编码,如果需要文件上传,请配置为 `true` | 166 | 167 | ##### 使用计划 168 | 169 | 参考: https://cloud.tencent.com/document/product/628/14947 170 | 171 | | 参数名称 | 是否必选 | 类型 | 描述 | 172 | | ------------- | :------: | ------ | :------------------------------------------------------ | 173 | | usagePlanId | 否 | String | 用户自定义使用计划 ID | 174 | | usagePlanName | 否 | String | 用户自定义的使用计划名称 | 175 | | usagePlanDesc | 否 | String | 用户自定义的使用计划描述 | 176 | | maxRequestNum | 否 | Number | 请求配额总数,如果为空,将使用-1 作为默认值,表示不开启 | 177 | 178 | ##### API 密钥配置 179 | 180 | 参考: https://cloud.tencent.com/document/product/628/14916 181 | 182 | | 参数名称 | 类型 | 描述 | 183 | | ---------- | :----- | :------- | 184 | | secretName | String | 密钥名称 | 185 | | secretIds | String | 密钥 ID | 186 | 187 | ##### 自定义域名 188 | 189 | Refer to: https://cloud.tencent.com/document/product/628/14906 190 | 191 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 192 | | ---------------- | :------: | :------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 193 | | domain | 是 | String | | 待绑定的自定义的域名。 | 194 | | certificateId | 否 | String | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 `https`,则为必选 | 195 | | isDefaultMapping | 否 | String | `true` | 是否使用默认路径映射。为 `false` 时,表示自定义路径映射,此时 pathMappingSet 必填。 | 196 | | pathMappingSet | 否 | Object[] | `[]` | 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 | 197 | | protocol | 否 | String[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 | 198 | 199 | - 自定义路径映射 200 | 201 | | 参数名称 | 是否必选 | 类型 | Description | 202 | | ----------- | :------: | :----- | :------------- | 203 | | path | 是 | String | 自定义映射路径 | 204 | | environment | 是 | String | 自定义映射环境 | 205 | -------------------------------------------------------------------------------- /docs/output.md: -------------------------------------------------------------------------------- 1 | # 部署 `output` 参数介绍 2 | 3 | > 组件输出可以在别的组件中通过 `${output:${stage}:${app}:.}` 获取 4 | > 5 | > 例如,如果该组件名称是 `test_name`, ·且只部署于一个地域,则可以通过 `${output:${stage}:${app}:test_name.apigw.url}` 在别的组件中获取该组件的 API 网关的 `url`。 6 | 7 | | 名称 | 类型 | 描述 | 8 | | :---------- | :------------------------------------------------------------------------------: | :------------------------------- | 9 | | templateUrl | string | 未提供代码时的模板代码 url | 10 | | region | string | 地域信息(只有一个地域时才提供) | 11 | | scf | [`FunctionOutput | Record`](#云函数输出-`FunctionOutput`) | 云函数输出信息 | 12 | | apigw | [`ApigwOutput | Record`](#API-网关输出-`ApigwOutput`) | API 网关输出信息 | 13 | 14 | ## 云函数输出 `FunctionOutput` 15 | 16 | | 名称 | 类型 | 描述 | 17 | | :------------------- | :------------: | :--------------------- | 18 | | functionName | string | 云函数名称 | 19 | | runtime | string | 云运行环境 | 20 | | namespace | string | 云函数名称空间 | 21 | | lastVersion | string | 云函数版本 | 22 | | traffic | `number (0~1)` | 将多少流量导向该云函数 | 23 | | configTrafficVersion | string | | 24 | 25 | ## API 网关输出 `ApigwOutput` 26 | 27 | | 名称 | 类型 | 描述 | 28 | | :------------ | :------------------------------------------------------------------: | :------------------------- | 29 | | serviceId | string | API 网关 ID | 30 | | subDomain | string | API 网关子域名 | 31 | | enviroment | `"release" | "prepub" | "test"` | API 网关 | 32 | | url | string | API 网关对外的完整 URL | 33 | | traffic | number (0~1) | 将多少流量导向该云函数 | 34 | | customDomains | [CustomDomain[]](#API-网关自定义域名输出-`ApigwOutput.CustomDomain`) | API 网关自定义域名输出列表 | 35 | 36 | ## API 网关自定义域名输出 `ApigwOutput.CustomDomain` 37 | 38 | | 名称 | 类型 | 描述 | 39 | | :--------------- | :----------------------------------------------------------------: | :------------------------- | 40 | | domain | string | 自定义域名 | 41 | | certificateId | string | 域名证书 ID | 42 | | isDefaultMapping | boolean | 该自定义域名是否为默认域名 | 43 | | pathMappingSet | [PathMapping[]](#-API-网关域名映射规则-`CustomDomain.PathMapping`) | 该域名的路径映射规则列表 | 44 | | protocols | `"http" | "https"` | 启用的协议 | 45 | 46 | ## API 网关域名映射规则 `CustomDomain.PathMapping` 47 | 48 | | 名称 | 类型 | 描述 | 49 | | :--------- | :----: | :--------------- | 50 | | path | string | 路径 | 51 | | enviroment | string | 路径映射到的环境 | 52 | -------------------------------------------------------------------------------- /docs/upload.md: -------------------------------------------------------------------------------- 1 | ## 文件上传说明 2 | 3 | 项目中如果涉及到文件上传,需要依赖 API 网关提供的 [Base64 编码能力](https://cloud.tencent.com/document/product/628/51799),使用时只需要 `serverless.yml` 中配置 `isBase64Encoded` 为 `true`,如下: 4 | 5 | ```yaml 6 | app: appDemo 7 | stage: dev 8 | component: express 9 | name: expressDemo 10 | 11 | inputs: 12 | # 省略... 13 | apigatewayConf: 14 | isBase64Encoded: true 15 | # 省略... 16 | # 省略... 17 | ``` 18 | 19 | 当前 API 网关支持上传最大文件大小为 `2M`,如果文件过大,请修改为前端直传对象存储方案。 20 | 21 | ## Base64 示例 22 | 23 | 此 Github 项目的 `example` 目录下存在模板文件: 24 | 25 | - [sls.upload.js](../example/sls.upload.js) 26 | 27 | 开发者可根据个人项目需要参考修改,使用时需要复制文件名为 `sls.js`。 28 | 29 | 文件中实现了文件上传接口 `POST /upload`,如果要支持文件上传,需要安装 `multer` 包。 30 | 31 | 同时需要在 `serverless.yml` 的 `apigatewayConf` 中配置 `isBase64Encoded` 为 `true`。 32 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | [![Serverless Components](https://img.serverlesscloud.cn/2020210/1581352135771-express.png)](http://serverless.com) 2 | 3 |
4 | 5 | **腾讯云 Express 组件** ⎯⎯⎯ 通过使用 [Tencent Serverless Framework](https://github.com/serverless/components/tree/cloud),基于云上 Serverless 服务(如网关、云函数等),实现“0”配置,便捷开发,极速部署你的 Express 应用,Express 组件支持丰富的配置扩展,提供了目前最易用、低成本并且弹性伸缩的 Express 项目开发/托管能力。 6 |
7 | 8 | 特性介绍: 9 | 10 | - [x] **按需付费** - 按照请求的使用量进行收费,没有请求时无需付费 11 | - [x] **"0"配置** - 只需要关心项目代码,之后部署即可,Serverless Framework 会搞定所有配置。 12 | - [x] **极速部署** - 仅需几秒,部署你的整个 Express 应用。 13 | - [x] **实时日志** - 通过实时日志的输出查看业务状态,便于直接在云端开发应用。 14 | - [x] **云端调试** - 针对 Node.js 框架支持一键云端调试能力,屏蔽本地环境的差异。 15 | - [x] **便捷协作** - 通过云端的状态信息和部署日志,方便的进行多人协作开发。 16 | - [x] **自定义域名** - 支持配置自定义域名及 HTTPS 访问 17 | 18 |
19 | 20 | 21 | 22 | 快速开始: 23 | 24 | 1. [**安装**](#1-安装) 25 | 2. [**创建**](#2-创建) 26 | 3. [**部署**](#3-部署) 27 | 4. [**配置**](#4-配置) 28 | 5. [**开发调试**](#5-开发调试) 29 | 6. [**查看状态**](#6-查看状态) 30 | 7. [**移除**](#7-移除) 31 | 32 | 更多资源: 33 | 34 | - [**架构说明**](#架构说明) 35 | - [**账号配置**](#账号配置) 36 | 37 |   38 | 39 | ### 1. 安装 40 | 41 | 通过 npm 安装最新版本的 Serverless Framework 42 | 43 | ```bash 44 | $ npm install -g serverless 45 | ``` 46 | 47 | ### 2. 创建 48 | 49 | 通过如下命令和模板链接,快速创建一个 Express 应用: 50 | 51 | ```bash 52 | $ serverless create --template-url https://github.com/serverless-components/tencent-express/tree/master/example 53 | $ cd example 54 | ``` 55 | 56 | 执行如下命令,安装 Express 应用的对应依赖 57 | 58 | ``` 59 | $ npm install 60 | ``` 61 | 62 | ### 3. 部署 63 | 64 | 在 `serverless.yml` 文件下的目录中运行 `serverless deploy` 进行 Express 项目的部署。第一次部署可能耗时相对较久,但后续的二次部署会在几秒钟之内完成。部署完毕后,你可以在命令行的输出中查看到你 Express 应用的 URL 地址,点击地址即可访问你的 Express 项目。 65 | 66 | **注意:** 67 | 68 | 如您的账号未[登陆](https://cloud.tencent.com/login)或[注册](https://cloud.tencent.com/register)腾讯云,您可以直接通过`微信`扫描命令行中的二维码进行授权登陆和注册。 69 | 70 | 如果出现了 `internal server error` 的报错,请检查是否在创建模板后没有运行 `npm install`。 71 | 72 | 如果希望查看更多部署过程的信息,可以通过`sls deploy --debug` 命令查看部署过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。 73 | 74 |
75 | 76 | ### 4. 配置 77 | 78 | Express 组件支持 0 配置部署,也就是可以直接通过配置文件中的默认值进行部署。但你依然可以修改更多可选配置来进一步开发该 Express 项目。 79 | 80 | 以下是 Express 组件的 `serverless.yml`完整配置说明: 81 | 82 | ```yml 83 | # serverless.yml 84 | 85 | component: express # (required) name of the component. In that case, it's express. 86 | name: expressDemo # (required) name of your express component instance. 87 | org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup. 88 | app: appDemo # (optional) serverless dashboard app. default is the same as the name property. 89 | stage: dev # (optional) serverless dashboard stage. default is dev. 90 | 91 | inputs: 92 | src: 93 | src: ./ # (optional) path to the source folder. default is a hello world app. 94 | exclude: 95 | - .env 96 | functionName: expressDemo 97 | region: ap-guangzhou 98 | runtime: Nodejs10.15 99 | apigatewayConf: 100 | protocols: 101 | - http 102 | - https 103 | environment: release 104 | ``` 105 | 106 | 点此查看[全量配置及配置说明](https://github.com/serverless-components/tencent-express/tree/master/docs/configure.md) 107 | 108 | 当你根据该配置文件更新配置字段后,再次运行 `serverless deploy` 或者 `serverless` 就可以更新配置到云端。 109 | 110 | ### 5. 开发调试 111 | 112 | 部署了 Express.js 应用后,可以通过开发调试能力对该项目进行二次开发,从而开发一个生产应用。在本地修改和更新代码后,不需要每次都运行 `serverless deploy` 命令来反复部署。你可以直接通过 `serverless dev` 命令对本地代码的改动进行检测和自动上传。 113 | 114 | 可以通过在 `serverless.yml`文件所在的目录下运行 `serverless dev` 命令开启开发调试能力。 115 | 116 | `serverless dev` 同时支持实时输出云端日志,每次部署完毕后,对项目进行访问,即可在命令行中实时输出调用日志,便于查看业务情况和排障。 117 | 118 | 除了实时日志输出之外,针对 Node.js 应用,当前也支持云端调试能力。在开启 `serverless dev` 命令之后,将会自动监听远端端口,并将函数的超时时间临时配置为 900s。此时你可以通过访问 chrome://inspect/#devices 查找远端的调试路径,并直接对云端代码进行断点等调试。在调试模式结束后,需要再次部署从而将代码更新并将超时时间设置为原来的值。详情参考[开发模式和云端调试](https://cloud.tencent.com/document/product/1154/43220)。 119 | 120 | ### 6. 查看状态 121 | 122 | 在`serverless.yml`文件所在的目录下,通过如下命令查看部署状态: 123 | 124 | ``` 125 | $ serverless info 126 | ``` 127 | 128 | ### 7. 移除 129 | 130 | 在`serverless.yml`文件所在的目录下,通过以下命令移除部署的 Express 服务。移除后该组件会对应删除云上部署时所创建的所有相关资源。 131 | 132 | ``` 133 | $ serverless remove 134 | ``` 135 | 136 | 和部署类似,支持通过 `sls remove --debug` 命令查看移除过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。 137 | 138 | ## 架构说明 139 | 140 | Express 组件将在腾讯云账户中使用到如下 Serverless 服务: 141 | 142 | - [x] **API 网关** - API 网关将会接收外部请求并且转发到 SCF 云函数中。 143 | - [x] **SCF 云函数** - 云函数将承载 Express.js 应用。 144 | - [x] **CAM 访问控制** - 该组件会创建默认 CAM 角色用于授权访问关联资源。 145 | - [x] **COS 对象存储** - 为确保上传速度和质量,云函数压缩并上传代码时,会默认将代码包存储在特定命名的 COS 桶中。 146 | - [x] **SSL 证书服务** - 如果你在 yaml 文件中配置了 `apigatewayConf.customDomains` 字段,需要做自定义域名绑定并开启 HTTPS 时,也会用到证书管理服务和域名服务。Serverless Framework 会根据已经备案的域名自动申请并配置 SSL 证书。 147 | 148 | ## 账号配置 149 | 150 | 当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件 151 | 152 | ```console 153 | $ touch .env # 腾讯云的配置信息 154 | ``` 155 | 156 | 在 `.env` 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存 157 | 158 | 如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。 159 | 160 | 如果已有腾讯云账号,可以在[API 密钥管理](https://console.cloud.tencent.com/cam/capi)中获取 `SecretId` 和`SecretKey`. 161 | 162 | ``` 163 | # .env 164 | TENCENT_SECRET_ID=123 165 | TENCENT_SECRET_KEY=123 166 | ``` 167 | 168 | ## License 169 | 170 | MIT License 171 | 172 | Copyright (c) 2020 Tencent Cloud, Inc. 173 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Serverless Component - Express.js 7 | 14 | 15 | 16 |

17 | Welcome to Express.js application created by 18 | Serverless Framework. 19 |

20 | 21 | 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "sls.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "yugasun", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^4.17.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/serverless.yml: -------------------------------------------------------------------------------- 1 | org: orgDemo 2 | app: appDemo 3 | stage: dev 4 | component: express 5 | name: expressDemo 6 | 7 | inputs: 8 | src: 9 | src: ./ 10 | exclude: 11 | - .env 12 | apigatewayConf: 13 | protocols: 14 | - http 15 | - https 16 | -------------------------------------------------------------------------------- /example/sls.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const app = express() 4 | const isServerless = process.env.SERVERLESS 5 | 6 | // Routes 7 | app.get(`/`, (req, res) => { 8 | res.sendFile(path.join(__dirname, 'index.html')) 9 | }) 10 | 11 | app.get('/user', (req, res) => { 12 | res.send([ 13 | { 14 | title: 'serverless framework', 15 | link: 'https://serverless.com' 16 | } 17 | ]) 18 | }) 19 | 20 | app.get('/user/:id', (req, res) => { 21 | const id = req.params.id 22 | res.send({ 23 | id: id, 24 | title: 'serverless framework', 25 | link: 'https://serverless.com' 26 | }) 27 | }) 28 | 29 | app.get('/404', (req, res) => { 30 | res.status(404).send('Not found') 31 | }) 32 | 33 | app.get('/500', (req, res) => { 34 | res.status(500).send('Server Error') 35 | }) 36 | 37 | // Error handler 38 | app.use(function(err, req, res, next) { 39 | console.error(err) 40 | res.status(500).send('Internal Serverless Error') 41 | }) 42 | 43 | if (isServerless) { 44 | module.exports = app 45 | } else { 46 | app.listen(3000, () => { 47 | console.log(`Server start on http://localhost:3000`) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /example/sls.upload.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer') 2 | const express = require('express') 3 | const path = require('path') 4 | 5 | const app = express() 6 | const isServerless = process.env.SERVERLESS 7 | const upload = multer({ dest: isServerless ? '/tmp/upload' : './upload' }) 8 | 9 | // Routes 10 | app.post('/upload', upload.single('file'), (req, res) => { 11 | res.send({ 12 | success: true, 13 | data: req.file 14 | }) 15 | }) 16 | 17 | app.get(`/`, (req, res) => { 18 | res.sendFile(path.join(__dirname, 'index.html')) 19 | }) 20 | 21 | app.get('/user', (req, res) => { 22 | res.send([ 23 | { 24 | title: 'serverless framework', 25 | link: 'https://serverless.com' 26 | } 27 | ]) 28 | }) 29 | 30 | app.get('/user/:id', (req, res) => { 31 | const id = req.params.id 32 | res.send({ 33 | id: id, 34 | title: 'serverless framework', 35 | link: 'https://serverless.com' 36 | }) 37 | }) 38 | 39 | app.get('/404', (req, res) => { 40 | res.status(404).send('Not found') 41 | }) 42 | 43 | app.get('/500', (req, res) => { 44 | res.status(500).send('Server Error') 45 | }) 46 | 47 | // Error handler 48 | app.use(function(err, req, res, next) { 49 | console.error(err) 50 | res.status(500).send('Internal Serverless Error') 51 | }) 52 | 53 | if (isServerless) { 54 | module.exports = app 55 | } else { 56 | app.listen(3000, () => { 57 | console.log(`Server start on http://localhost:3000`) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | require('dotenv').config({ path: join(__dirname, '.env.test') }) 3 | 4 | const config = { 5 | verbose: true, 6 | silent: false, 7 | testTimeout: 600000, 8 | testEnvironment: 'node', 9 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$', 10 | testPathIgnorePatterns: ['/node_modules/', '/__tests__/lib/'], 11 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 12 | } 13 | 14 | module.exports = config 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless/express", 3 | "main": "src/serverless.js", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "scripts": { 8 | "test": "jest", 9 | "commitlint": "commitlint -f HEAD@{15}", 10 | "lint": "eslint --ext .js,.ts,.tsx .", 11 | "lint:fix": "eslint --fix --ext .js,.ts,.tsx .", 12 | "prettier": "prettier --check '**/*.{css,html,js,json,md,yaml,yml}'", 13 | "prettier:fix": "prettier --write '**/*.{css,html,js,json,md,yaml,yml}'", 14 | "release": "semantic-release", 15 | "release-local": "node -r dotenv/config node_modules/semantic-release/bin/semantic-release --no-ci --dry-run", 16 | "check-dependencies": "npx npm-check --skip-unused --update" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "ygsec && lint-staged", 21 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 22 | "pre-push": "ygsec && npm run lint:fix && npm run prettier:fix" 23 | } 24 | }, 25 | "lint-staged": { 26 | "**/*.{js,ts,tsx}": [ 27 | "npm run lint:fix", 28 | "git add ." 29 | ], 30 | "**/*.{css,html,js,json,md,yaml,yml}": [ 31 | "npm run prettier:fix", 32 | "git add ." 33 | ] 34 | }, 35 | "author": "Tencent Cloud, Inc.", 36 | "license": "MIT", 37 | "dependencies": {}, 38 | "devDependencies": { 39 | "@commitlint/cli": "^8.3.5", 40 | "@commitlint/config-conventional": "^8.3.4", 41 | "@semantic-release/changelog": "^5.0.0", 42 | "@semantic-release/commit-analyzer": "^8.0.1", 43 | "@semantic-release/git": "^9.0.0", 44 | "@semantic-release/npm": "^7.0.4", 45 | "@semantic-release/release-notes-generator": "^9.0.1", 46 | "@serverless/platform-client-china": "^1.0.19", 47 | "@ygkit/secure": "0.0.3", 48 | "axios": "^0.19.2", 49 | "babel-eslint": "^10.1.0", 50 | "dotenv": "^8.2.0", 51 | "eslint": "^6.8.0", 52 | "eslint-config-prettier": "^6.10.0", 53 | "eslint-plugin-import": "^2.20.1", 54 | "eslint-plugin-prettier": "^3.1.2", 55 | "husky": "^4.2.5", 56 | "jest": "^25.0.1", 57 | "lint-staged": "^10.0.8", 58 | "prettier": "^1.19.1", 59 | "semantic-release": "^17.0.4" 60 | }, 61 | "description": "Easily deploy serverless Express.js applications to Tencent Cloud with the Serverless Framework", 62 | "directories": { 63 | "doc": "docs", 64 | "example": "example", 65 | "test": "tests" 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/serverless-components/tencent-express.git" 70 | }, 71 | "keywords": [ 72 | "serverless-express", 73 | "serverless", 74 | "express", 75 | "serverless-framework", 76 | "serverless-components", 77 | "tencent-cloud" 78 | ], 79 | "bugs": { 80 | "url": "https://github.com/serverless-components/tencent-express/issues" 81 | }, 82 | "homepage": "https://github.com/serverless-components/tencent-express#readme" 83 | } 84 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | printWidth: 100, 4 | semi: false, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'none' 8 | } 9 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verifyConditions: [ 3 | '@semantic-release/changelog', 4 | '@semantic-release/git', 5 | '@semantic-release/github' 6 | ], 7 | plugins: [ 8 | [ 9 | '@semantic-release/commit-analyzer', 10 | { 11 | preset: 'angular', 12 | parserOpts: { 13 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'] 14 | } 15 | } 16 | ], 17 | [ 18 | '@semantic-release/release-notes-generator', 19 | { 20 | preset: 'angular', 21 | parserOpts: { 22 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'] 23 | }, 24 | writerOpts: { 25 | commitsSort: ['subject', 'scope'] 26 | } 27 | } 28 | ], 29 | [ 30 | '@semantic-release/changelog', 31 | { 32 | changelogFile: 'CHANGELOG.md' 33 | } 34 | ], 35 | [ 36 | '@semantic-release/git', 37 | { 38 | assets: ['package.json', 'src/**', 'CHANGELOG.md'], 39 | message: 'chore(release): version ${nextRelease.version} \n\n${nextRelease.notes}' 40 | } 41 | ], 42 | [ 43 | '@semantic-release/github', 44 | { 45 | assets: ['!.env'] 46 | } 47 | ] 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /serverless.component.yml: -------------------------------------------------------------------------------- 1 | name: express 2 | version: 0.3.0 3 | author: Tencent Cloud, Inc. 4 | org: Tencent Cloud, Inc. 5 | description: Deploy a serverless Express.js application on Tencent SCF and API Gateway. 6 | keywords: tencent, serverless, express 7 | repo: https://github.com/serverless-components/tencent-express 8 | readme: https://github.com/serverless-components/tencent-express/tree/master/README.md 9 | license: MIT 10 | main: ./src 11 | webDeployable: true 12 | -------------------------------------------------------------------------------- /src/_shims/handler.js: -------------------------------------------------------------------------------- 1 | require('tencent-component-monitor') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const { createServer, proxy } = require('tencent-serverless-http') 5 | 6 | let server 7 | let app 8 | 9 | exports.handler = async (event, context) => { 10 | const userSls = path.join(__dirname, '..', process.env.SLS_ENTRY_FILE) 11 | if (fs.existsSync(userSls)) { 12 | // eslint-disable-next-line 13 | console.log(`Using user custom entry file ${process.env.SLS_ENTRY_FILE}`) 14 | app = require(userSls) 15 | } else { 16 | app = require('./sls.js') 17 | } 18 | 19 | // attach event and context to request 20 | app.request.__SLS_EVENT__ = event 21 | app.request.__SLS_CONTEXT__ = context 22 | 23 | // provide sls intialize hooks 24 | if (app.slsInitialize && typeof app.slsInitialize === 'function') { 25 | await app.slsInitialize() 26 | } 27 | 28 | // cache server, not create repeatly 29 | if (!server) { 30 | server = createServer(app, null, app.binaryTypes || []) 31 | } 32 | 33 | context.callbackWaitsForEmptyEventLoop = app.callbackWaitsForEmptyEventLoop === true 34 | 35 | const result = await proxy(server, event, context, 'PROMISE') 36 | return result.promise 37 | } 38 | -------------------------------------------------------------------------------- /src/_shims/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless/express", 3 | "main": "./handler.js", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint . --fix --cache" 10 | }, 11 | "author": "Serverless, Inc.", 12 | "license": "Apache", 13 | "dependencies": { 14 | "tencent-component-monitor": "^1.1.0", 15 | "tencent-serverless-http": "^1.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/_shims/sls.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | // Routes 5 | app.get(`/`, (req, res) => { 6 | res.send({ 7 | msg: `Hello Express, Request received: ${req.method} - ${req.path}` 8 | }) 9 | }) 10 | 11 | // Error handler 12 | // eslint-disable-next-line 13 | app.use(function(err, req, res, next) { 14 | console.error(err) 15 | res.status(500).send('Internal Serverless Error') 16 | }) 17 | 18 | module.exports = app 19 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const CONFIGS = { 2 | templateUrl: 3 | 'https://serverless-templates-1300862921.cos.ap-beijing.myqcloud.com/express-demo.zip', 4 | compName: 'express', 5 | compFullname: 'Express.js', 6 | defaultEntryFile: 'sls.js', 7 | handler: 'sl_handler.handler', 8 | runtime: 'Nodejs10.15', 9 | timeout: 3, 10 | memorySize: 128, 11 | namespace: 'default', 12 | description: 'Created by Serverless Component' 13 | } 14 | 15 | module.exports = CONFIGS 16 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "download": "^8.0.0", 4 | "tencent-component-toolkit": "^1.20.10", 5 | "type": "^2.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/serverless.js: -------------------------------------------------------------------------------- 1 | const { Component } = require('@serverless/core') 2 | const { Scf, Apigw, Cns, Cam, Metrics } = require('tencent-component-toolkit') 3 | const { TypeError } = require('tencent-component-toolkit/src/utils/error') 4 | const { uploadCodeToCos, getDefaultProtocol, prepareInputs, deepClone } = require('./utils') 5 | const CONFIGS = require('./config') 6 | 7 | class ServerlessComponent extends Component { 8 | getCredentials() { 9 | const { tmpSecrets } = this.credentials.tencent 10 | 11 | if (!tmpSecrets || !tmpSecrets.TmpSecretId) { 12 | throw new TypeError( 13 | 'CREDENTIAL', 14 | 'Cannot get secretId/Key, your account could be sub-account and does not have the access to use SLS_QcsRole, please make sure the role exists first, then visit https://cloud.tencent.com/document/product/1154/43006, follow the instructions to bind the role to your account.' 15 | ) 16 | } 17 | 18 | return { 19 | SecretId: tmpSecrets.TmpSecretId, 20 | SecretKey: tmpSecrets.TmpSecretKey, 21 | Token: tmpSecrets.Token 22 | } 23 | } 24 | 25 | getAppId() { 26 | return this.credentials.tencent.tmpSecrets.appId 27 | } 28 | 29 | async deployFunction(credentials, inputs, regionList) { 30 | if (!inputs.role) { 31 | try { 32 | const camClient = new Cam(credentials) 33 | const roleExist = await camClient.CheckSCFExcuteRole() 34 | if (roleExist) { 35 | inputs.role = 'QCS_SCFExcuteRole' 36 | } 37 | } catch (e) { 38 | // no op 39 | } 40 | } 41 | 42 | const outputs = {} 43 | const appId = this.getAppId() 44 | 45 | const funcDeployer = async (curRegion) => { 46 | const code = await uploadCodeToCos(this, appId, credentials, inputs, curRegion) 47 | const scf = new Scf(credentials, curRegion) 48 | const tempInputs = { 49 | ...inputs, 50 | code 51 | } 52 | const scfOutput = await scf.deploy(deepClone(tempInputs)) 53 | outputs[curRegion] = { 54 | functionName: scfOutput.FunctionName, 55 | runtime: scfOutput.Runtime, 56 | namespace: scfOutput.Namespace 57 | } 58 | 59 | this.state[curRegion] = { 60 | ...(this.state[curRegion] ? this.state[curRegion] : {}), 61 | ...outputs[curRegion] 62 | } 63 | 64 | // default version is $LATEST 65 | outputs[curRegion].lastVersion = scfOutput.LastVersion 66 | ? scfOutput.LastVersion 67 | : this.state.lastVersion || '$LATEST' 68 | 69 | // default traffic is 1.0, it can also be 0, so we should compare to undefined 70 | outputs[curRegion].traffic = 71 | scfOutput.Traffic !== undefined 72 | ? scfOutput.Traffic 73 | : this.state.traffic !== undefined 74 | ? this.state.traffic 75 | : 1 76 | 77 | if (outputs[curRegion].traffic !== 1 && scfOutput.ConfigTrafficVersion) { 78 | outputs[curRegion].configTrafficVersion = scfOutput.ConfigTrafficVersion 79 | this.state.configTrafficVersion = scfOutput.ConfigTrafficVersion 80 | } 81 | 82 | this.state.lastVersion = outputs[curRegion].lastVersion 83 | this.state.traffic = outputs[curRegion].traffic 84 | } 85 | 86 | for (let i = 0; i < regionList.length; i++) { 87 | const curRegion = regionList[i] 88 | await funcDeployer(curRegion) 89 | } 90 | this.save() 91 | return outputs 92 | } 93 | 94 | // try to add dns record 95 | async tryToAddDnsRecord(credentials, customDomains) { 96 | try { 97 | const cns = new Cns(credentials) 98 | for (let i = 0; i < customDomains.length; i++) { 99 | const item = customDomains[i] 100 | if (item.domainPrefix) { 101 | await cns.deploy({ 102 | domain: item.subDomain.replace(`${item.domainPrefix}.`, ''), 103 | records: [ 104 | { 105 | subDomain: item.domainPrefix, 106 | recordType: 'CNAME', 107 | recordLine: '默认', 108 | value: item.cname, 109 | ttl: 600, 110 | mx: 10, 111 | status: 'enable' 112 | } 113 | ] 114 | }) 115 | } 116 | } 117 | } catch (e) { 118 | console.log('METHOD_tryToAddDnsRecord', e.message) 119 | } 120 | } 121 | 122 | async deployApigateway(credentials, inputs, regionList) { 123 | if (inputs.isDisabled) { 124 | return {} 125 | } 126 | 127 | const getServiceId = (instance, region) => { 128 | const regionState = instance.state[region] 129 | return inputs.serviceId || (regionState && regionState.serviceId) 130 | } 131 | 132 | const deployTasks = [] 133 | const outputs = {} 134 | regionList.forEach((curRegion) => { 135 | const apigwDeployer = async () => { 136 | const apigw = new Apigw(credentials, curRegion) 137 | 138 | const oldState = this.state[curRegion] || {} 139 | const apigwInputs = { 140 | ...inputs, 141 | oldState: { 142 | apiList: oldState.apiList || [], 143 | customDomains: oldState.customDomains || [] 144 | } 145 | } 146 | // different region deployment has different service id 147 | apigwInputs.serviceId = getServiceId(this, curRegion) 148 | const apigwOutput = await apigw.deploy(deepClone(apigwInputs)) 149 | outputs[curRegion] = { 150 | serviceId: apigwOutput.serviceId, 151 | subDomain: apigwOutput.subDomain, 152 | environment: apigwOutput.environment, 153 | url: `${getDefaultProtocol(inputs.protocols)}://${apigwOutput.subDomain}/${ 154 | apigwOutput.environment 155 | }${apigwInputs.endpoints[0].path}` 156 | } 157 | 158 | if (apigwOutput.customDomains) { 159 | // TODO: need confirm add cns authentication 160 | if (inputs.autoAddDnsRecord === true) { 161 | // await this.tryToAddDnsRecord(credentials, apigwOutput.customDomains) 162 | } 163 | outputs[curRegion].customDomains = apigwOutput.customDomains 164 | } 165 | this.state[curRegion] = { 166 | created: true, 167 | ...(this.state[curRegion] ? this.state[curRegion] : {}), 168 | ...outputs[curRegion], 169 | apiList: apigwOutput.apiList 170 | } 171 | } 172 | deployTasks.push(apigwDeployer()) 173 | }) 174 | 175 | await Promise.all(deployTasks) 176 | 177 | this.save() 178 | return outputs 179 | } 180 | 181 | async deploy(inputs) { 182 | console.log(`Deploying ${CONFIGS.compFullname} App...`) 183 | 184 | const credentials = this.getCredentials() 185 | 186 | // 对Inputs内容进行标准化 187 | const { regionList, functionConf, apigatewayConf } = await prepareInputs( 188 | this, 189 | credentials, 190 | inputs 191 | ) 192 | 193 | // 部署函数 + API网关 194 | const outputs = {} 195 | if (!functionConf.code.src) { 196 | outputs.templateUrl = CONFIGS.templateUrl 197 | } 198 | 199 | let apigwOutputs 200 | const functionOutputs = await this.deployFunction( 201 | credentials, 202 | functionConf, 203 | regionList, 204 | outputs 205 | ) 206 | // support apigatewayConf.isDisabled 207 | if (apigatewayConf.isDisabled !== true) { 208 | apigwOutputs = await this.deployApigateway(credentials, apigatewayConf, regionList, outputs) 209 | } else { 210 | this.state.apigwDisabled = true 211 | } 212 | 213 | // optimize outputs for one region 214 | if (regionList.length === 1) { 215 | const [oneRegion] = regionList 216 | outputs.region = oneRegion 217 | outputs['scf'] = functionOutputs[oneRegion] 218 | if (apigwOutputs) { 219 | outputs['apigw'] = apigwOutputs[oneRegion] 220 | } 221 | } else { 222 | outputs['scf'] = functionOutputs 223 | if (apigwOutputs) { 224 | outputs['apigw'] = apigwOutputs 225 | } 226 | } 227 | 228 | this.state.region = regionList[0] 229 | this.state.regionList = regionList 230 | this.state.lambdaArn = functionConf.name 231 | 232 | return outputs 233 | } 234 | 235 | async remove() { 236 | console.log(`Removing ${CONFIGS.compFullname} App...`) 237 | 238 | const { state } = this 239 | const { regionList = [] } = state 240 | 241 | const credentials = this.getCredentials() 242 | 243 | const removeHandlers = [] 244 | for (let i = 0; i < regionList.length; i++) { 245 | const curRegion = regionList[i] 246 | const curState = state[curRegion] 247 | const scf = new Scf(credentials, curRegion) 248 | const apigw = new Apigw(credentials, curRegion) 249 | const handler = async () => { 250 | // if disable apigw, no need to remove 251 | if (state.apigwDisabled !== true) { 252 | await apigw.remove({ 253 | created: curState.created, 254 | environment: curState.environment, 255 | serviceId: curState.serviceId, 256 | apiList: curState.apiList, 257 | customDomains: curState.customDomains 258 | }) 259 | } 260 | await scf.remove({ 261 | functionName: curState.functionName, 262 | namespace: curState.namespace 263 | }) 264 | } 265 | removeHandlers.push(handler()) 266 | } 267 | 268 | await Promise.all(removeHandlers) 269 | 270 | if (this.state.cns) { 271 | const cns = new Cns(credentials) 272 | for (let i = 0; i < this.state.cns.length; i++) { 273 | await cns.remove({ deleteList: this.state.cns[i].records }) 274 | } 275 | } 276 | 277 | this.state = {} 278 | } 279 | 280 | async metrics(inputs = {}) { 281 | console.log(`Get ${CONFIGS.compFullname} Metrics Datas...`) 282 | if (!inputs.rangeStart || !inputs.rangeEnd) { 283 | throw new TypeError( 284 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, 285 | 'rangeStart and rangeEnd are require inputs' 286 | ) 287 | } 288 | const { region } = this.state 289 | if (!region) { 290 | throw new TypeError( 291 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, 292 | 'No region property in state' 293 | ) 294 | } 295 | const { functionName, namespace, functionVersion } = this.state[region] || {} 296 | if (functionName) { 297 | const options = { 298 | funcName: functionName, 299 | namespace: namespace, 300 | version: functionVersion, 301 | region, 302 | timezone: inputs.tz 303 | } 304 | 305 | const curState = this.state[region] 306 | if (curState.serviceId) { 307 | options.apigwServiceId = curState.serviceId 308 | options.apigwEnvironment = curState.environment || 'release' 309 | } 310 | const credentials = this.getCredentials() 311 | const mertics = new Metrics(credentials, options) 312 | const metricResults = await mertics.getDatas( 313 | inputs.rangeStart, 314 | inputs.rangeEnd, 315 | Metrics.Type.All 316 | ) 317 | return metricResults 318 | } 319 | throw new TypeError( 320 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, 321 | 'Function name not define' 322 | ) 323 | } 324 | } 325 | 326 | module.exports = ServerlessComponent 327 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { Cos } = require('tencent-component-toolkit') 3 | const ensureObject = require('type/object/ensure') 4 | const ensureIterable = require('type/iterable/ensure') 5 | const ensureString = require('type/string/ensure') 6 | const download = require('download') 7 | const { TypeError } = require('tencent-component-toolkit/src/utils/error') 8 | const CONFIGS = require('./config') 9 | 10 | /* 11 | * Generates a random id 12 | */ 13 | const generateId = () => 14 | Math.random() 15 | .toString(36) 16 | .substring(6) 17 | 18 | const deepClone = (obj) => { 19 | return JSON.parse(JSON.stringify(obj)) 20 | } 21 | 22 | const getType = (obj) => { 23 | return Object.prototype.toString.call(obj).slice(8, -1) 24 | } 25 | 26 | const mergeJson = (sourceJson, targetJson) => { 27 | Object.entries(sourceJson).forEach(([key, val]) => { 28 | targetJson[key] = deepClone(val) 29 | }) 30 | return targetJson 31 | } 32 | 33 | const capitalString = (str) => { 34 | if (str.length < 2) { 35 | return str.toUpperCase() 36 | } 37 | 38 | return `${str[0].toUpperCase()}${str.slice(1)}` 39 | } 40 | 41 | const getDefaultProtocol = (protocols) => { 42 | return String(protocols).includes('https') ? 'https' : 'http' 43 | } 44 | 45 | const getDefaultFunctionName = () => { 46 | return `${CONFIGS.compName}_component_${generateId()}` 47 | } 48 | 49 | const getDefaultServiceName = () => { 50 | return 'serverless' 51 | } 52 | 53 | const getDefaultServiceDescription = () => { 54 | return 'Created by Serverless Component' 55 | } 56 | 57 | const validateTraffic = (num) => { 58 | if (getType(num) !== 'Number') { 59 | throw new TypeError( 60 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, 61 | 'traffic must be a number' 62 | ) 63 | } 64 | if (num < 0 || num > 1) { 65 | throw new TypeError( 66 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, 67 | 'traffic must be a number between 0 and 1' 68 | ) 69 | } 70 | return true 71 | } 72 | 73 | const getCodeZipPath = async (instance, inputs) => { 74 | console.log(`Packaging ${CONFIGS.compFullname} application...`) 75 | 76 | // unzip source zip file 77 | let zipPath 78 | if (!inputs.code.src) { 79 | // add default template 80 | const downloadPath = `/tmp/${generateId()}` 81 | const filename = 'template' 82 | 83 | console.log(`Installing Default ${CONFIGS.compFullname} App...`) 84 | try { 85 | await download(CONFIGS.templateUrl, downloadPath, { 86 | filename: `${filename}.zip` 87 | }) 88 | } catch (e) { 89 | throw new TypeError(`DOWNLOAD_TEMPLATE`, 'Download default template failed.') 90 | } 91 | zipPath = `${downloadPath}/${filename}.zip` 92 | } else { 93 | zipPath = inputs.code.src 94 | } 95 | 96 | return zipPath 97 | } 98 | 99 | /** 100 | * Upload code to COS 101 | * @param {Component} instance serverless component instance 102 | * @param {string} appId app id 103 | * @param {object} credentials credentials 104 | * @param {object} inputs component inputs parameters 105 | * @param {string} region region 106 | */ 107 | const uploadCodeToCos = async (instance, appId, credentials, inputs, region) => { 108 | const bucketName = inputs.code.bucket || `sls-cloudfunction-${region}-code` 109 | const objectName = inputs.code.object || `${inputs.name}-${Math.floor(Date.now() / 1000)}.zip` 110 | // if set bucket and object not pack code 111 | if (!inputs.code.bucket || !inputs.code.object) { 112 | const zipPath = await getCodeZipPath(instance, inputs) 113 | console.log(`Code zip path ${zipPath}`) 114 | 115 | // save the zip path to state for lambda to use it 116 | instance.state.zipPath = zipPath 117 | 118 | const cos = new Cos(credentials, region) 119 | 120 | if (!inputs.code.bucket) { 121 | // create default bucket 122 | await cos.deploy({ 123 | bucket: bucketName + '-' + appId, 124 | force: true, 125 | lifecycle: [ 126 | { 127 | status: 'Enabled', 128 | id: 'deleteObject', 129 | filter: '', 130 | expiration: { days: '10' }, 131 | abortIncompleteMultipartUpload: { daysAfterInitiation: '10' } 132 | } 133 | ] 134 | }) 135 | } 136 | 137 | // upload code to cos 138 | if (!inputs.code.object) { 139 | console.log(`Getting cos upload url for bucket ${bucketName}`) 140 | const uploadUrl = await cos.getObjectUrl({ 141 | bucket: bucketName + '-' + appId, 142 | object: objectName, 143 | method: 'PUT' 144 | }) 145 | 146 | // if shims and sls sdk entries had been injected to zipPath, no need to injected again 147 | console.log(`Uploading code to bucket ${bucketName}`) 148 | if (instance.codeInjected === true) { 149 | await instance.uploadSourceZipToCOS(zipPath, uploadUrl, {}, {}) 150 | } else { 151 | const slsSDKEntries = instance.getSDKEntries('_shims/handler.handler') 152 | await instance.uploadSourceZipToCOS(zipPath, uploadUrl, slsSDKEntries, { 153 | _shims: path.join(__dirname, '_shims') 154 | }) 155 | instance.codeInjected = true 156 | } 157 | console.log(`Upload ${objectName} to bucket ${bucketName} success`) 158 | } 159 | } 160 | 161 | // save bucket state 162 | instance.state.bucket = bucketName 163 | instance.state.object = objectName 164 | 165 | return { 166 | bucket: bucketName, 167 | object: objectName 168 | } 169 | } 170 | 171 | const prepareInputs = async (instance, credentials, inputs = {}) => { 172 | // 对function inputs进行标准化 173 | const tempFunctionConf = inputs.functionConf 174 | ? inputs.functionConf 175 | : inputs.functionConfig 176 | ? inputs.functionConfig 177 | : {} 178 | const fromClientRemark = `tencent-${CONFIGS.compName}` 179 | const regionList = inputs.region 180 | ? typeof inputs.region == 'string' 181 | ? [inputs.region] 182 | : inputs.region 183 | : ['ap-guangzhou'] 184 | 185 | // chenck state function name 186 | const stateFunctionName = 187 | instance.state[regionList[0]] && instance.state[regionList[0]].functionName 188 | const functionConf = Object.assign(tempFunctionConf, { 189 | code: { 190 | src: inputs.src, 191 | bucket: inputs.srcOriginal && inputs.srcOriginal.bucket, 192 | object: inputs.srcOriginal && inputs.srcOriginal.object 193 | }, 194 | name: 195 | ensureString(inputs.functionName, { isOptional: true }) || 196 | stateFunctionName || 197 | getDefaultFunctionName(), 198 | region: regionList, 199 | role: ensureString(tempFunctionConf.role ? tempFunctionConf.role : inputs.role, { 200 | default: '' 201 | }), 202 | handler: ensureString(tempFunctionConf.handler ? tempFunctionConf.handler : inputs.handler, { 203 | default: CONFIGS.handler 204 | }), 205 | runtime: ensureString(tempFunctionConf.runtime ? tempFunctionConf.runtime : inputs.runtime, { 206 | default: CONFIGS.runtime 207 | }), 208 | namespace: ensureString( 209 | tempFunctionConf.namespace ? tempFunctionConf.namespace : inputs.namespace, 210 | { default: CONFIGS.namespace } 211 | ), 212 | description: ensureString( 213 | tempFunctionConf.description ? tempFunctionConf.description : inputs.description, 214 | { 215 | default: CONFIGS.description 216 | } 217 | ), 218 | fromClientRemark, 219 | layers: ensureIterable(tempFunctionConf.layers ? tempFunctionConf.layers : inputs.layers, { 220 | default: [] 221 | }), 222 | cfs: ensureIterable(tempFunctionConf.cfs ? tempFunctionConf.cfs : inputs.cfs, { 223 | default: [] 224 | }), 225 | publish: inputs.publish, 226 | traffic: inputs.traffic, 227 | lastVersion: instance.state.lastVersion, 228 | timeout: tempFunctionConf.timeout ? tempFunctionConf.timeout : CONFIGS.timeout, 229 | memorySize: tempFunctionConf.memorySize ? tempFunctionConf.memorySize : CONFIGS.memorySize, 230 | tags: ensureObject(tempFunctionConf.tags ? tempFunctionConf.tags : inputs.tag, { 231 | default: null 232 | }) 233 | }) 234 | 235 | // validate traffic 236 | if (inputs.traffic !== undefined) { 237 | validateTraffic(inputs.traffic) 238 | } 239 | functionConf.needSetTraffic = inputs.traffic !== undefined && functionConf.lastVersion 240 | 241 | if (tempFunctionConf.environment) { 242 | functionConf.environment = tempFunctionConf.environment 243 | functionConf.environment.variables = functionConf.environment.variables || {} 244 | functionConf.environment.variables.SERVERLESS = '1' 245 | functionConf.environment.variables.SLS_ENTRY_FILE = inputs.entryFile || CONFIGS.defaultEntryFile 246 | } else { 247 | functionConf.environment = { 248 | variables: { 249 | SERVERLESS: '1', 250 | SLS_ENTRY_FILE: inputs.entryFile || CONFIGS.defaultEntryFile 251 | } 252 | } 253 | } 254 | 255 | if (tempFunctionConf.vpcConfig) { 256 | functionConf.vpcConfig = tempFunctionConf.vpcConfig 257 | } 258 | 259 | // 对apigw inputs进行标准化 260 | const tempApigwConf = inputs.apigatewayConf 261 | ? inputs.apigatewayConf 262 | : inputs.apigwConfig 263 | ? inputs.apigwConfig 264 | : {} 265 | const apigatewayConf = Object.assign(tempApigwConf, { 266 | serviceId: inputs.serviceId || tempApigwConf.serviceId, 267 | region: regionList, 268 | isDisabled: tempApigwConf.isDisabled === true, 269 | fromClientRemark: fromClientRemark, 270 | serviceName: inputs.serviceName || tempApigwConf.serviceName || getDefaultServiceName(instance), 271 | serviceDesc: tempApigwConf.serviceDesc || getDefaultServiceDescription(instance), 272 | protocols: tempApigwConf.protocols || ['http'], 273 | environment: tempApigwConf.environment ? tempApigwConf.environment : 'release', 274 | customDomains: tempApigwConf.customDomains || [] 275 | }) 276 | if (!apigatewayConf.endpoints) { 277 | apigatewayConf.endpoints = [ 278 | { 279 | path: tempApigwConf.path || '/', 280 | enableCORS: tempApigwConf.enableCORS, 281 | serviceTimeout: tempApigwConf.serviceTimeout, 282 | method: 'ANY', 283 | apiName: tempApigwConf.apiName || 'index', 284 | isBase64Encoded: tempApigwConf.isBase64Encoded, 285 | isBase64Trigger: tempApigwConf.isBase64Trigger, 286 | base64EncodedTriggerRules: tempApigwConf.base64EncodedTriggerRules, 287 | function: { 288 | isIntegratedResponse: true, 289 | functionName: functionConf.name, 290 | functionNamespace: functionConf.namespace, 291 | functionQualifier: 292 | (tempApigwConf.function && tempApigwConf.function.functionQualifier) || '$LATEST' 293 | } 294 | } 295 | ] 296 | } 297 | if (tempApigwConf.usagePlan) { 298 | apigatewayConf.endpoints[0].usagePlan = { 299 | usagePlanId: tempApigwConf.usagePlan.usagePlanId, 300 | usagePlanName: tempApigwConf.usagePlan.usagePlanName, 301 | usagePlanDesc: tempApigwConf.usagePlan.usagePlanDesc, 302 | maxRequestNum: tempApigwConf.usagePlan.maxRequestNum 303 | } 304 | } 305 | if (tempApigwConf.auth) { 306 | apigatewayConf.endpoints[0].auth = { 307 | secretName: tempApigwConf.auth.secretName, 308 | secretIds: tempApigwConf.auth.secretIds 309 | } 310 | } 311 | 312 | regionList.forEach((curRegion) => { 313 | const curRegionConf = inputs[curRegion] 314 | if (curRegionConf && curRegionConf.functionConf) { 315 | functionConf[curRegion] = curRegionConf.functionConf 316 | } 317 | if (curRegionConf && curRegionConf.apigatewayConf) { 318 | apigatewayConf[curRegion] = curRegionConf.apigatewayConf 319 | } 320 | }) 321 | 322 | return { 323 | regionList, 324 | functionConf, 325 | apigatewayConf 326 | } 327 | } 328 | 329 | module.exports = { 330 | deepClone, 331 | generateId, 332 | uploadCodeToCos, 333 | mergeJson, 334 | capitalString, 335 | getDefaultProtocol, 336 | prepareInputs 337 | } 338 | --------------------------------------------------------------------------------