diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..11c8ac8
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,48 @@
+name: Release
+
+on:
+ push:
+ branches: [master]
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ env:
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ persist-credentials: false
+
+ - name: Install Node.js and npm
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+ registry-url: https://registry.npmjs.org
+
+ - name: Retrieve dependencies from cache
+ id: cacheNpm
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.npm
+ node_modules
+ key: npm-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('package.json') }}
+ restore-keys: npm-v14-${{ runner.os }}-refs/heads/master-
+
+ - name: Install dependencies
+ if: steps.cacheNpm.outputs.cache-hit != 'true'
+ run: |
+ npm update --no-save
+ npm update --save-dev --no-save
+ - name: Releasing
+ run: |
+ npm run release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+ GIT_AUTHOR_NAME: slsplus
+ GIT_AUTHOR_EMAIL: slsplus.sz@gmail.com
+ GIT_COMMITTER_NAME: slsplus
+ GIT_COMMITTER_EMAIL: slsplus.sz@gmail.com
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..c966e94
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,47 @@
+name: Test
+
+on:
+ pull_request:
+ branches: [master]
+
+jobs:
+ test:
+ name: Test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ # Ensure connection with 'master' branch
+ fetch-depth: 2
+
+ - name: Install Node.js and npm
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+ registry-url: https://registry.npmjs.org
+
+ - name: Retrieve dependencies from cache
+ id: cacheNpm
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.npm
+ node_modules
+ key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }}
+ restore-keys: |
+ npm-v14-${{ runner.os }}-${{ github.ref }}-
+ npm-v14-${{ runner.os }}-refs/heads/master-
+
+ - name: Install dependencies
+ if: steps.cacheNpm.outputs.cache-hit != 'true'
+ run: |
+ npm update --no-save
+ npm update --save-dev --no-save
+ - name: Running tests
+ run: npm run test
+ env:
+ SERVERLESS_PLATFORM_VENDOR: tencent
+ GLOBAL_ACCELERATOR_NA: true
+ TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }}
+ TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 0000000..3840792
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,45 @@
+name: Validate
+
+on:
+ pull_request:
+ branches: [master]
+
+jobs:
+ lintAndFormatting:
+ name: Lint & Formatting
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ # Ensure connection with 'master' branch
+ fetch-depth: 2
+
+ - name: Install Node.js and npm
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+ registry-url: https://registry.npmjs.org
+
+ - name: Retrieve dependencies from cache
+ id: cacheNpm
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.npm
+ node_modules
+ key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }}
+ restore-keys: |
+ npm-v14-${{ runner.os }}-${{ github.ref }}-
+ npm-v14-${{ runner.os }}-refs/heads/master-
+
+ - name: Install dependencies
+ if: steps.cacheNpm.outputs.cache-hit != 'true'
+ run: |
+ npm update --no-save
+ npm update --save-dev --no-save
+
+ - name: Validate Formatting
+ run: npm run prettier:fix
+ - name: Validate Lint rules
+ run: npm run lint:fix
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b9f070c..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-language: node_js
-
-node_js:
- - 8
- - 10
-
-install:
- - npm install
-
-# should change to serverless registry publish
-jobs:
- include:
- # Define the release stage that runs semantic-release
- - stage: release
- node_js: 10.18
- # Advanced: optionally overwrite your default `script` step to skip the tests
- # script: skip
- deploy:
- provider: script
- skip_cleanup: true
- script:
- - npm run release
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..96dfed8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,132 @@
+# [0.3.0](https://github.com/serverless-components/tencent-express/compare/v0.2.3...v0.3.0) (2021-01-26)
+
+
+### Features
+
+* support apigw base64 encode ([78ca266](https://github.com/serverless-components/tencent-express/commit/78ca26613eec65558852ff1b6a130a841b0178f3))
+
+## [0.2.3](https://github.com/serverless-components/tencent-express/compare/v0.2.2...v0.2.3) (2020-12-28)
+
+
+### Bug Fixes
+
+* add sls initialize before creating server ([7f18f2c](https://github.com/serverless-components/tencent-express/commit/7f18f2c5e8e86cc0a9252e5bd036742251964e4d))
+
+## [0.2.2](https://github.com/serverless-components/tencent-express/compare/v0.2.1...v0.2.2) (2020-12-15)
+
+
+### Bug Fixes
+
+* update remove flow ([f766ee6](https://github.com/serverless-components/tencent-express/commit/f766ee69cf39cf410df0203fc71644297ef70693))
+
+## [0.2.1](https://github.com/serverless-components/tencent-express/compare/v0.2.0...v0.2.1) (2020-12-15)
+
+
+### Bug Fixes
+
+* update to deployment to serial flow ([a76fe58](https://github.com/serverless-components/tencent-express/commit/a76fe586846269132f3a9b9141302050b02a13c3))
+
+# [0.2.0](https://github.com/serverless-components/tencent-express/compare/v0.1.5...v0.2.0) (2020-10-12)
+
+
+### Bug Fixes
+
+* support all parameters for apigw ([ec3ffc7](https://github.com/serverless-components/tencent-express/commit/ec3ffc7fb6a6959dbdb734b8a2137f5ac777d05c))
+
+
+### Features
+
+* support custom entry file and all scf config ([80e2890](https://github.com/serverless-components/tencent-express/commit/80e28903422c5710a56a8267f64ffe47398ddcf8))
+
+## [0.1.5](https://github.com/serverless-components/tencent-express/compare/v0.1.4...v0.1.5) (2020-09-03)
+
+
+### Bug Fixes
+
+* update deploy flow for multi region ([33ae212](https://github.com/serverless-components/tencent-express/commit/33ae21248a7c191e3ceea2371fd2e69a6b1ebf67))
+
+## [0.1.4](https://github.com/serverless-components/tencent-express/compare/v0.1.3...v0.1.4) (2020-09-02)
+
+
+### Bug Fixes
+
+* update tencnet-component-toolkit for api mark ([7e25e8a](https://github.com/serverless-components/tencent-express/commit/7e25e8a86f539b2db449caf969cbbd58c5af4d1b))
+
+## [0.1.3](https://github.com/serverless-components/tencent-express/compare/v0.1.2...v0.1.3) (2020-09-01)
+
+
+### Bug Fixes
+
+* support cfs ([9d1a1e1](https://github.com/serverless-components/tencent-express/commit/9d1a1e1793d866f5fbe5bdce61090db8381bb103))
+* update deps for error message ([f0a8f89](https://github.com/serverless-components/tencent-express/commit/f0a8f892f469efcda67070ff940421aa83a100c0))
+
+## [0.1.2](https://github.com/serverless-components/tencent-express/compare/v0.1.1...v0.1.2) (2020-08-26)
+
+
+### Bug Fixes
+
+* add apigw/scf deployed state saving manually ([0fcc0b8](https://github.com/serverless-components/tencent-express/commit/0fcc0b84bdeb7119086cdbb050ae2c8ab734ff32))
+* add regionList state ([f66d8d8](https://github.com/serverless-components/tencent-express/commit/f66d8d8ee6564e343442bb59b9996bf23fd475b9))
+* add release config ([26c7e12](https://github.com/serverless-components/tencent-express/commit/26c7e1201caf79830843e860237f3ebf6efdf1e7))
+* apigw custom domain update bug ([1a53027](https://github.com/serverless-components/tencent-express/commit/1a53027effde871dc9f021f510fd7a13cc86794a))
+* apigw isDisabled ([f624171](https://github.com/serverless-components/tencent-express/commit/f624171b927f0efd4605566c6168d67a3f9b8302))
+* bind unexist role bug ([6cd8990](https://github.com/serverless-components/tencent-express/commit/6cd89900111c7afd1e073d95f81f99364debfb96))
+* cache http server ([fc5c824](https://github.com/serverless-components/tencent-express/commit/fc5c82453e2469d66837e1b525d317e811b3115a))
+* change entry to sls.js ([61bd482](https://github.com/serverless-components/tencent-express/commit/61bd4820dfcacefa1338580fa8d3b0f2609ffe4c))
+* cns ([65aa8b8](https://github.com/serverless-components/tencent-express/commit/65aa8b8b42880c9709ff583429899275978b007e))
+* enableCORS for apigw ([a0f59ef](https://github.com/serverless-components/tencent-express/commit/a0f59ef01d5dfe500758adfe2c4f05d7ffc2f22b))
+* handle usageplan & auth undefined ([2a257d5](https://github.com/serverless-components/tencent-express/commit/2a257d564e0968c8db1e0a4030412749cb19e847))
+* make default runtime to 10.15 ([a295bd4](https://github.com/serverless-components/tencent-express/commit/a295bd49a9b48f470a3416026531e840dd2177ac))
+* metics data bug ([6bdf89b](https://github.com/serverless-components/tencent-express/commit/6bdf89bbe3b3665012acee5ca66e08fdff9f05db))
+* metrics qps limit ([dddb9f6](https://github.com/serverless-components/tencent-express/commit/dddb9f607c96d86a3500eddeae248d600e2c9813))
+* monitor timeout bug ([de33311](https://github.com/serverless-components/tencent-express/commit/de333111a692b5bc8d14e9274b81b5b2e5e57e84))
+* optimize outputs for one region ([b3c6964](https://github.com/serverless-components/tencent-express/commit/b3c69646528edfdb8ff0d6455a4dc2b16e13cb81))
+* optimize traffic config outputs ([8545ead](https://github.com/serverless-components/tencent-express/commit/8545ead58183454536ca43c412d42d9be9c05580))
+* package version ([d682b66](https://github.com/serverless-components/tencent-express/commit/d682b66ab7858bb85c29548f90514056f8c34749))
+* prettier config ([e17bd6f](https://github.com/serverless-components/tencent-express/commit/e17bd6fe939666f7d8c5c89182ed24125409e068))
+* read bucket and object from srcOriginal ([1b2cf9d](https://github.com/serverless-components/tencent-express/commit/1b2cf9d7c890a94e7deaa830814b9758ac42cdf4))
+* release v0.0.1 for v2 ([52b2a37](https://github.com/serverless-components/tencent-express/commit/52b2a37a9f57550d48af868b6d53ac3a9c020fd5))
+* remove credential bug ([bca2d1f](https://github.com/serverless-components/tencent-express/commit/bca2d1f9c6ab79ee7b29844fe8bdf2085ddac94c))
+* role check error ([1a0b4bf](https://github.com/serverless-components/tencent-express/commit/1a0b4bf3ec1fed5492d72f07378ca7b5d26bbfe8))
+* support apigw endpoint timeout config ([01dd4c9](https://github.com/serverless-components/tencent-express/commit/01dd4c931235ec5547ce5f34e0997beb4e094fd1))
+* support eip config ([62c597f](https://github.com/serverless-components/tencent-express/commit/62c597fdef0a8e296b1943dba13baa60725afd53))
+* template url output ([88df485](https://github.com/serverless-components/tencent-express/commit/88df48503bd1f106e451ce102ec5bd16d6a1442a))
+* throw error when no temp secrets ([2a9167e](https://github.com/serverless-components/tencent-express/commit/2a9167e98a9648fb3ed7264956b10ea2808ce490))
+* traffic zero display bug ([0285406](https://github.com/serverless-components/tencent-express/commit/0285406c812284bd8b5847f0c201c9b69761c9d6))
+* uniform throw error ([c09b6f5](https://github.com/serverless-components/tencent-express/commit/c09b6f5b0071a2fb6a3251b0f8f31bc0dd614d3b))
+* update deploy code ([90a4339](https://github.com/serverless-components/tencent-express/commit/90a433968502f9d1499534cf852c6d4c3db12ba6))
+* update deps ([2e551db](https://github.com/serverless-components/tencent-express/commit/2e551db26dcfa02adf5aa2c63d6c08fea771ed00))
+* update deps ([c14e8f1](https://github.com/serverless-components/tencent-express/commit/c14e8f1492fa8ca7e3bedbc8018571556aa0c6ff))
+* update error message ([c4c7243](https://github.com/serverless-components/tencent-express/commit/c4c724358fe5be700e596929b8bddcfa94faa6fd))
+* update get credential error message ([ed47b3d](https://github.com/serverless-components/tencent-express/commit/ed47b3d759944e41b0c8fa7641594450d88704de))
+* update toolkit verison ([2e99bc7](https://github.com/serverless-components/tencent-express/commit/2e99bc73c438318d125d50d959bf3f9de4a6e85e))
+* update usageplan & auth logic ([afa8807](https://github.com/serverless-components/tencent-express/commit/afa8807792aa0e69c191391b1fb49b059add3c35))
+* upgrade deps ([6a1f7a8](https://github.com/serverless-components/tencent-express/commit/6a1f7a86d4838d02cef19de37a2f6e9714ee640b))
+* upgrade deps ([d5c013b](https://github.com/serverless-components/tencent-express/commit/d5c013b6a04487a29b9848442d31cc71c6230c5f))
+* upgrade tencent-component-toolkit ([4ba2edf](https://github.com/serverless-components/tencent-express/commit/4ba2edf8570e511b9fd5a8286ae81044b531c094))
+* upgrade tencent-component-toolkit ([af8c234](https://github.com/serverless-components/tencent-express/commit/af8c234622058253a21ebe0231796dd7a47902f8))
+* upgrade tencent-component-toolkit ([bdd5062](https://github.com/serverless-components/tencent-express/commit/bdd5062f6501fbe91e167ce3e7a91da2e8eb4acd))
+* upgrade tencent-component-toolkit ([00f4a09](https://github.com/serverless-components/tencent-express/commit/00f4a0993d04d4a7be3cf8ea15381a6d8ff04c98))
+* upgrade tencent-component-toolkit for deleting compatibility ([fa215bf](https://github.com/serverless-components/tencent-express/commit/fa215bfb66264c360c24a3aca68f4b63cb4c60fd))
+* wrong region parameter bug ([#31](https://github.com/serverless-components/tencent-express/issues/31)) ([32e519c](https://github.com/serverless-components/tencent-express/commit/32e519ca00170ed75f0bea19b80c0255288e8d37))
+* 增加cns ([b452285](https://github.com/serverless-components/tencent-express/commit/b45228501a381dd6ede9381ca8d05cd5f403d585))
+* 增加cns ([a2b87d9](https://github.com/serverless-components/tencent-express/commit/a2b87d92908935dc4299dfb0d5b01029c5fc8649))
+
+
+### Features
+
+* add binary types support ([60603d6](https://github.com/serverless-components/tencent-express/commit/60603d61bbeb1cbdcf44d5bb6b9cd5a6f2b08fd6))
+* add integration test ([7c28271](https://github.com/serverless-components/tencent-express/commit/7c2827162f94b817f5e8f3c00e279263bff120e7))
+* add layers config support ([d971be9](https://github.com/serverless-components/tencent-express/commit/d971be985daf3bc4127c6648fd0e474c535ad1eb))
+* add metrics api ([#27](https://github.com/serverless-components/tencent-express/issues/27)) ([0d40154](https://github.com/serverless-components/tencent-express/commit/0d40154347b5db735fcac66a8a9b5ccf0e6ca035))
+* add state store, and remove method ([8815e40](https://github.com/serverless-components/tencent-express/commit/8815e4045cf7896e8843c017abb16b24720caa79))
+* optimize code zip flow ([f2d60ce](https://github.com/serverless-components/tencent-express/commit/f2d60ce03e96257f4aacda464ab83a4dc144fecb))
+* optimize deploy log ([80a2bc0](https://github.com/serverless-components/tencent-express/commit/80a2bc0a23a08051ede3c5d54657833499e0d2fc))
+* optimize metics and support disable apigw creating ([e722c80](https://github.com/serverless-components/tencent-express/commit/e722c80d3d1fce8315e08a86808c131402a7a81b))
+* support api gw metrics ([cc625ad](https://github.com/serverless-components/tencent-express/commit/cc625ad3e8a410b161b4744813685aa42a700898))
+* support role config ([f1d6ed6](https://github.com/serverless-components/tencent-express/commit/f1d6ed664b7354376f5791bd162113cdd44b7b5b))
+* support scf publish version and traffic setup ([8807d0e](https://github.com/serverless-components/tencent-express/commit/8807d0ef0fd8c560b3c7aaa8fa37165473a5f863))
+* update config and support usageplan+auth ([b067a14](https://github.com/serverless-components/tencent-express/commit/b067a149acec78d97f701aea893e95ad3833f7c3))
+* update event and context attach method ([31d0c1d](https://github.com/serverless-components/tencent-express/commit/31d0c1de42354103a800e0515f9a069971b357df))
+* 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))
+* using temporary secret for credentials ([cf61300](https://github.com/serverless-components/tencent-express/commit/cf61300f7d94f3149648a0d3378b8876a440aca1))
diff --git a/README.md b/README.md
index 651a98e..9fb1b9d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+⚠️⚠️⚠️ 所有框架组件项目迁移到 [tencent-framework-components](https://github.com/serverless-components/tencent-framework-components).
+
[](http://serverless.com)
@@ -17,8 +19,6 @@
-
-
快速开始:
1. [**安装**](#1-安装)
@@ -49,35 +49,31 @@ $ npm install -g serverless
通过如下命令和模板链接,快速创建一个 Express 应用:
```bash
-$ serverless create --template-url https://github.com/serverless-components/tencent-express/tree/v2/serverless-express
-$ cd serverless-express
-```
-
-执行如下命令,安装 Express 应用的对应依赖
-
-```
-$ npm install
+$ serverless init express-starter --name example
+$ cd example
```
### 3. 部署
-在 `serverless.yml` 文件下的目录中运行 `serverless deploy` 进行 Express 项目的部署。第一次部署可能耗时相对较久,但后续的二次部署会在几秒钟之内完成。部署完毕后,你可以在命令行的输出中查看到你 Express 应用的 URL 地址,点击地址即可访问你的 Express 项目。
+在 `serverless.yml` 文件所在的项目根目录,运行以下指令进行部署:
-**注意:**
+```bash
+$ serverless deploy
+```
-如您的账号未[登陆](https://cloud.tencent.com/login)或[注册](https://cloud.tencent.com/register)腾讯云,您可以直接通过`微信`扫描命令行中的二维码进行授权登陆和注册。
+部署时需要进行身份验证,如您的账号未 [登陆](https://cloud.tencent.com/login) 或 [注册](https://cloud.tencent.com/register) 腾讯云,您可以直接通过 `微信` 扫描命令行中的二维码进行授权登陆和注册。
-如果出现了 `internal server error` 的报错,请检查是否在创建模板后没有运行 `npm install`。
+> 注意: 如果希望查看更多部署过程的信息,可以通过`serverless deploy --debug` 命令查看部署过程中的实时日志信息。
-如果希望查看更多部署过程的信息,可以通过`sls deploy --debug` 命令查看部署过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。
+部署完成后,控制台会打印相关的输出信息,您可以通过 `${output:${stage}:${app}:apigw.url}` 的形式在其他 `serverless` 组件中引用该组件的 API 网关访问链接(或通过类似的形式引用该组建其他输出结果),具体的,可以查看完成的输出文档:
-
+- [点击此处查看输出文档](https://github.com/serverless-components/tencent-express/tree/master/docs/output.md)
### 4. 配置
Express 组件支持 0 配置部署,也就是可以直接通过配置文件中的默认值进行部署。但你依然可以修改更多可选配置来进一步开发该 Express 项目。
-以下是 Express 组件的 `serverless.yml`完整配置说明:
+以下是 Express 组件的 `serverless.yml`配置示例:
```yml
# serverless.yml
@@ -89,12 +85,13 @@ app: appDemo # (optional) serverless dashboard app. default is the same as the n
stage: dev # (optional) serverless dashboard stage. default is dev.
inputs:
- src: ./ # (optional) path to the source folder. default is a hello world app.
+ src:
+ src: ./ # (optional) path to the source folder. default is a hello world app.
+ exclude:
+ - .env
functionName: expressDemo
region: ap-guangzhou
runtime: Nodejs10.15
- exclude:
- - .env
apigatewayConf:
protocols:
- http
@@ -102,7 +99,7 @@ inputs:
environment: release
```
-点此查看[全量配置及配置说明](https://github.com/serverless-components/tencent-express/blob/v2/docs/configure.md)
+点此查看[全量配置及配置说明](https://github.com/serverless-components/tencent-express/tree/master/docs/configure.md)
当你根据该配置文件更新配置字段后,再次运行 `serverless deploy` 或者 `serverless` 就可以更新配置到云端。
@@ -132,7 +129,7 @@ $ serverless info
$ serverless remove
```
-和部署类似,支持通过 `sls remove --debug` 命令查看移除过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。
+和部署类似,支持通过 `serverless remove --debug` 命令查看移除过程中的实时日志信息。
## 架构说明
@@ -164,6 +161,52 @@ TENCENT_SECRET_ID=123
TENCENT_SECRET_KEY=123
```
+## 静态资源服务
+
+如果想要支持返回静态资源,比如图片之类的,需要在入口文件 `sls.js` 中指定相关 `MIME` 类型的文件为二进制,这样云函数在返回请求结果给 API 网关是,会对指定类型进行 `Base64` 编码,最终返回给客户端才能正常显示。如下:
+
+```js
+const express = require('express')
+const app = express()
+
+// Routes
+// ...
+
+app.binaryTypes = ['*/*']
+
+module.exports = app
+```
+
+`['*/*']` 代表所有文件类型将进行 `Base64` 编码,如果需要定制化,可以配置为 `['image/png']`,意思是指定 `png` 格式的图片类型。
+
+更多文件类型的 `MIME` 类型,可参考 [mime-db](https://github.com/jshttp/mime-db/blob/master/db.json)。
+
+### slsInitialize 应用初始化
+
+有些时候,Express 服务在启动前,需要进行一个初始化操作,比如数据库建连,就可以通过在 Express 实例对象上添加 `slsInitialize` 函数来实现,如下:
+
+```js
+const express = require('express')
+const mysql = require('mysql2/promise')
+
+const app = new express()
+
+// ...
+
+app.slsInitialize = async () => {
+ app.db = await mysql.createConnection({
+ host: 'localhost',
+ user: 'root',
+ database: 'test'
+ })
+}
+
+// don't forget to export!
+module.exports = app
+```
+
+这样应用部署到云函数后,在函数服务逻辑执行前,会先执行 `slsInitialize()` 函数,来初始化数据库连接。
+
## License
MIT License
diff --git a/__tests__/index.test.js b/__tests__/index.test.js
new file mode 100644
index 0000000..2e67e22
--- /dev/null
+++ b/__tests__/index.test.js
@@ -0,0 +1,62 @@
+const { join } = require('path');
+require('dotenv').config({ path: join(__dirname, '.env.test') });
+
+const { generateId, getServerlessSdk } = require('./lib/utils')
+const execSync = require('child_process').execSync
+const path = require('path')
+const axios = require('axios')
+
+const instanceYaml = {
+ org: 'orgDemo',
+ app: 'appDemo',
+ component: 'express@dev',
+ name: `express-integration-tests-${generateId()}`,
+ stage: 'dev',
+ inputs: {
+ region: 'ap-guangzhou',
+ runtime: 'Nodejs10.15',
+ apigatewayConf: { environment: 'test' }
+ }
+}
+
+const credentials = {
+ tencent: {
+ SecretId: process.env.TENCENT_SECRET_ID,
+ SecretKey: process.env.TENCENT_SECRET_KEY,
+ }
+}
+
+const sdk = getServerlessSdk(instanceYaml.org)
+
+it('should successfully deploy express app', async () => {
+ const instance = await sdk.deploy(instanceYaml, credentials)
+
+ expect(instance).toBeDefined()
+ expect(instance.instanceName).toEqual(instanceYaml.name)
+ expect(instance.outputs.templateUrl).toBeDefined()
+ expect(instance.outputs.region).toEqual(instanceYaml.inputs.region)
+ expect(instance.outputs.apigw).toBeDefined()
+ expect(instance.outputs.apigw.environment).toEqual(instanceYaml.inputs.apigatewayConf.environment)
+ expect(instance.outputs.scf).toBeDefined()
+ expect(instance.outputs.scf.runtime).toEqual(instanceYaml.inputs.runtime)
+})
+
+it('should successfully update source code', async () => {
+ // change source to own source './src' and need to install packages before deploy
+ const srcPath = path.join(__dirname, '..', 'example')
+ execSync('npm install', { cwd: srcPath })
+ instanceYaml.inputs.src = srcPath
+
+ const instance = await sdk.deploy(instanceYaml, credentials)
+ const response = await axios.get(instance.outputs.apigw.url)
+
+ expect(response.data.includes('Serverless Framework')).toBeTruthy()
+ expect(instance.outputs.templateUrl).not.toBeDefined()
+})
+
+it('should successfully remove express app', async () => {
+ await sdk.remove(instanceYaml, credentials)
+ result = await sdk.getInstance(instanceYaml.org, instanceYaml.stage, instanceYaml.app, instanceYaml.name)
+
+ expect(result.instance.instanceStatus).toEqual('inactive')
+})
diff --git a/__tests__/lib/utils.js b/__tests__/lib/utils.js
new file mode 100644
index 0000000..d047afa
--- /dev/null
+++ b/__tests__/lib/utils.js
@@ -0,0 +1,24 @@
+const { ServerlessSDK } = require('@serverless/platform-client-china')
+
+/*
+ * Generate random id
+ */
+const generateId = () =>
+ Math.random()
+ .toString(36)
+ .substring(6)
+
+/*
+ * Initializes and returns an instance of the serverless sdk
+ * @param ${string} orgName - the serverless org name.
+ */
+const getServerlessSdk = (orgName) => {
+ const sdk = new ServerlessSDK({
+ context: {
+ orgName
+ }
+ })
+ return sdk
+}
+
+module.exports = { generateId, getServerlessSdk }
diff --git a/docs/configure.md b/docs/configure.md
index 28f4c0e..392f030 100644
--- a/docs/configure.md
+++ b/docs/configure.md
@@ -17,6 +17,7 @@ inputs:
serviceName: mytest # api网关服务名称
runtime: Nodejs10.15 # 运行环境
serviceId: service-np1uloxw # api网关服务ID
+ entryFile: sls.js # 自定义 server 的入口文件名,默认为 sls.js,如果不想修改文件名为 sls.js 可以自定义
src: ./src # 第一种为string时,会打包src对应目录下的代码上传到默认cos上。
# src: # 第二种,部署src下的文件代码,并打包成zip上传到bucket上
# src: ./src # 本地需要打包的文件目录
@@ -30,9 +31,9 @@ inputs:
layers:
- name: layerName # layer名称
version: 1 # 版本
- traffic: 0.9 # 配置默认流量中 $LATEST 版本比重:0 - 1
functionConf: # 函数配置相关
timeout: 10 # 超时时间,单位秒
+ eip: false # 是否固定出口IP
memorySize: 128 # 内存大小,单位MB
environment: # 环境变量
variables: # 环境变量数组
@@ -46,6 +47,8 @@ inputs:
customDomains: # 自定义域名绑定
- domain: abc.com # 待绑定的自定义的域名
certificateId: abcdefg # 待绑定自定义域名的证书唯一 ID
+ # 如要设置自定义路径映射,请设置为 false
+ isDefaultMapping: false
# 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。
pathMappingSet:
- path: /
@@ -58,6 +61,7 @@ inputs:
- https
environment: test
serviceTimeout: 15
+ isBase64Encoded: false
usagePlan: # 用户使用计划
usagePlanId: 1111
usagePlanName: slscmp
@@ -73,20 +77,20 @@ inputs:
主要的参数
-| 参数名称 | 是否必选 | 默认值 | 描述 |
-| ------------------------------------ | :------: | :-------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| runtime | 否 | Nodejs10.15 | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16.16 |
-| region | 否 | ap-guangzhou | 项目部署所在区域,默认广州区 |
-| functionName | 否 | | 云函数名称 |
-| serviceName | 否 | | API 网关服务名称, 默认创建一个新的服务名称 |
-| serviceId | 否 | | API 网关服务 ID,如果存在将使用这个 API 网关服务 |
-| src | 否 | `process.cwd()` | 默认为当前目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) |
-| layers | 否 | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) |
-| traffic | 否 | 1 | 配置默认流量中 `$LATEST` 版本比重,取值范围:0 ~ 1,比如 80%,可配置成 0.8。注意如果皮遏制灰度流量,需要配置对应的 API 网关触发器的 endpoints 的 `function.functionQualifier` 参数为 `$DEFAULT` (默认流量) |
-| [functionConf](#函数配置) | 否 | | 函数配置 |
-| [apigatewayConf](#API-网关配置) | 否 | | API 网关配置 |
-| [cloudDNSConf](#DNS-配置) | 否 | | DNS 配置 |
-| [Region special config](#指定区配置) | 否 | | 指定区配置 |
+| 参数名称 | 必选 | 默认值 | 描述 |
+| ------------------------------------ | :--: | :-------------: | :------------------------------------------------------------------ |
+| runtime | 否 | `Nodejs10.15` | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16 |
+| region | 否 | `ap-guangzhou` | 项目部署所在区域,默认广州区 |
+| functionName | 否 | | 云函数名称 |
+| serviceName | 否 | | API 网关服务名称, 默认创建一个新的服务名称 |
+| serviceId | 否 | | API 网关服务 ID,如果存在将使用这个 API 网关服务 |
+| entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 |
+| src | 否 | `process.cwd()` | 默认为当前目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) |
+| layers | 否 | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) |
+| [functionConf](#函数配置) | 否 | | 函数配置 |
+| [apigatewayConf](#API-网关配置) | 否 | | API 网关配置 |
+| [cloudDNSConf](#DNS-配置) | 否 | | DNS 配置 |
+| [Region special config](#指定区配置) | 否 | | 指定区配置 |
## 执行目录
@@ -110,7 +114,7 @@ inputs:
| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
| ---------- | :------: | -------- | :----: | :---------------------------------------------- |
-| ttl | 否 | Number | 600 | TTL 值,范围 1 - 604800,不同等级域名最小值不同 |
+| ttl | 否 | Number | `600` | TTL 值,范围 1 - 604800,不同等级域名最小值不同 |
| recordLine | 否 | String[] | | 记录的线路名称 |
### 指定区配置
@@ -125,12 +129,13 @@ inputs:
参考: https://cloud.tencent.com/document/product/583/18586
-| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
-| ----------- | :------: | :----: | :----: | :------------------------------------------------------------------------------ |
-| timeout | 否 | Number | 3 | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 |
-| memorySize | 否 | Number | 128 | 函数运行时内存大小,默认为 128M,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 |
-| environment | 否 | Object | | 函数的环境变量, 参考 [环境变量](#环境变量) |
-| vpcConfig | 否 | Object | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) |
+| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
+| ----------- | :------: | :-----: | :-----: | :------------------------------------------------------------------------------ |
+| timeout | 否 | Number | `3` | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 |
+| memorySize | 否 | Number | `128` | 函数运行时内存大小,默认为 128M,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 |
+| environment | 否 | Object | | 函数的环境变量, 参考 [环境变量](#环境变量) |
+| vpcConfig | 否 | Object | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) |
+| eip | 否 | Boolean | `false` | 是否固定出口 IP |
##### 环境变量
@@ -147,16 +152,17 @@ inputs:
### API 网关配置
-| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
-| -------------- | :------: | :------- | :------- | :--------------------------------------------------------------------------------- |
-| protocols | 否 | String[] | ['http'] | 前端请求的类型,如 http,https,http 与 https |
-| environment | 否 | String | release | 发布环境. 目前支持三种发布环境: test(测试), prepub(预发布) 与 release(发布). |
-| usagePlan | 否 | | | 使用计划配置, 参考 [使用计划](#使用计划) |
-| auth | 否 | | | API 密钥配置, 参考 [API 密钥](#API-密钥配置) |
-| customDomain | 否 | Object[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) |
-| enableCORS | 否 | Boolean | `false` | 开启跨域。默认值为否。 |
-| serviceTimeout | 否 | Number | `15` | Api 超时时间,单位: 秒 |
-| isDisabled | 否 | Boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 |
+| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
+| --------------- | :------: | :------- | :--------- | :--------------------------------------------------------------------------------- |
+| protocols | 否 | String[] | `['http']` | 前端请求的类型,如 http,https,http 与 https |
+| environment | 否 | String | `release` | 发布环境. 目前支持三种发布环境: test(测试), prepub(预发布) 与 release(发布). |
+| usagePlan | 否 | | | 使用计划配置, 参考 [使用计划](#使用计划) |
+| auth | 否 | | | API 密钥配置, 参考 [API 密钥](#API-密钥配置) |
+| customDomain | 否 | Object[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) |
+| enableCORS | 否 | Boolean | `false` | 开启跨域。默认值为否。 |
+| serviceTimeout | 否 | Number | `15` | Api 超时时间,单位: 秒 |
+| isDisabled | 否 | Boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 |
+| isBase64Encoded | 否 | Boolean | `false` | 是否开启 Base64 编码,如果需要文件上传,请配置为 `true` |
##### 使用计划
@@ -167,7 +173,7 @@ inputs:
| usagePlanId | 否 | String | 用户自定义使用计划 ID |
| usagePlanName | 否 | String | 用户自定义的使用计划名称 |
| usagePlanDesc | 否 | String | 用户自定义的使用计划描述 |
-| maxRequestNum | 否 | Int | 请求配额总数,如果为空,将使用-1 作为默认值,表示不开启 |
+| maxRequestNum | 否 | Number | 请求配额总数,如果为空,将使用-1 作为默认值,表示不开启 |
##### API 密钥配置
@@ -182,13 +188,13 @@ inputs:
Refer to: https://cloud.tencent.com/document/product/628/14906
-| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
-| ---------------- | :------: | :------: | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| domain | 是 | String | | 待绑定的自定义的域名。 |
-| certificateId | 否 | String | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 https,则为必选 |
-| isDefaultMapping | 否 | String | `'TRUE'` | 是否使用默认路径映射,默认为 TRUE。为 FALSE 时,表示自定义路径映射,此时 pathMappingSet 必填。 |
-| pathMappingSet | 否 | Object[] | `[]` | 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 |
-| protocol | 否 | String[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 |
+| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 |
+| ---------------- | :------: | :------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| domain | 是 | String | | 待绑定的自定义的域名。 |
+| certificateId | 否 | String | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 `https`,则为必选 |
+| isDefaultMapping | 否 | String | `true` | 是否使用默认路径映射。为 `false` 时,表示自定义路径映射,此时 pathMappingSet 必填。 |
+| pathMappingSet | 否 | Object[] | `[]` | 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 |
+| protocol | 否 | String[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 |
- 自定义路径映射
diff --git a/docs/output.md b/docs/output.md
new file mode 100644
index 0000000..f9fdd99
--- /dev/null
+++ b/docs/output.md
@@ -0,0 +1,51 @@
+# 部署 `output` 参数介绍
+
+> 组件输出可以在别的组件中通过 `${output:${stage}:${app}:.}` 获取
+>
+> 例如,如果该组件名称是 `test_name`, ·且只部署于一个地域,则可以通过 `${output:${stage}:${app}:test_name.apigw.url}` 在别的组件中获取该组件的 API 网关的 `url`。
+
+| 名称 | 类型 | 描述 |
+| :---------- | :------------------------------------------------------------------------------: | :------------------------------- |
+| templateUrl | string | 未提供代码时的模板代码 url |
+| region | string | 地域信息(只有一个地域时才提供) |
+| scf | [`FunctionOutput | Record`](#云函数输出-`FunctionOutput`) | 云函数输出信息 |
+| apigw | [`ApigwOutput | Record`](#API-网关输出-`ApigwOutput`) | API 网关输出信息 |
+
+## 云函数输出 `FunctionOutput`
+
+| 名称 | 类型 | 描述 |
+| :------------------- | :------------: | :--------------------- |
+| functionName | string | 云函数名称 |
+| runtime | string | 云运行环境 |
+| namespace | string | 云函数名称空间 |
+| lastVersion | string | 云函数版本 |
+| traffic | `number (0~1)` | 将多少流量导向该云函数 |
+| configTrafficVersion | string | |
+
+## API 网关输出 `ApigwOutput`
+
+| 名称 | 类型 | 描述 |
+| :------------ | :------------------------------------------------------------------: | :------------------------- |
+| serviceId | string | API 网关 ID |
+| subDomain | string | API 网关子域名 |
+| enviroment | `"release" | "prepub" | "test"` | API 网关 |
+| url | string | API 网关对外的完整 URL |
+| traffic | number (0~1) | 将多少流量导向该云函数 |
+| customDomains | [CustomDomain[]](#API-网关自定义域名输出-`ApigwOutput.CustomDomain`) | API 网关自定义域名输出列表 |
+
+## API 网关自定义域名输出 `ApigwOutput.CustomDomain`
+
+| 名称 | 类型 | 描述 |
+| :--------------- | :----------------------------------------------------------------: | :------------------------- |
+| domain | string | 自定义域名 |
+| certificateId | string | 域名证书 ID |
+| isDefaultMapping | boolean | 该自定义域名是否为默认域名 |
+| pathMappingSet | [PathMapping[]](#-API-网关域名映射规则-`CustomDomain.PathMapping`) | 该域名的路径映射规则列表 |
+| protocols | `"http" | "https"` | 启用的协议 |
+
+## API 网关域名映射规则 `CustomDomain.PathMapping`
+
+| 名称 | 类型 | 描述 |
+| :--------- | :----: | :--------------- |
+| path | string | 路径 |
+| enviroment | string | 路径映射到的环境 |
diff --git a/docs/upload.md b/docs/upload.md
new file mode 100644
index 0000000..2e5e68d
--- /dev/null
+++ b/docs/upload.md
@@ -0,0 +1,31 @@
+## 文件上传说明
+
+项目中如果涉及到文件上传,需要依赖 API 网关提供的 [Base64 编码能力](https://cloud.tencent.com/document/product/628/51799),使用时只需要 `serverless.yml` 中配置 `isBase64Encoded` 为 `true`,如下:
+
+```yaml
+app: appDemo
+stage: dev
+component: express
+name: expressDemo
+
+inputs:
+ # 省略...
+ apigatewayConf:
+ isBase64Encoded: true
+ # 省略...
+ # 省略...
+```
+
+当前 API 网关支持上传最大文件大小为 `2M`,如果文件过大,请修改为前端直传对象存储方案。
+
+## Base64 示例
+
+此 Github 项目的 `example` 目录下存在模板文件:
+
+- [sls.upload.js](../example/sls.upload.js)
+
+开发者可根据个人项目需要参考修改,使用时需要复制文件名为 `sls.js`。
+
+文件中实现了文件上传接口 `POST /upload`,如果要支持文件上传,需要安装 `multer` 包。
+
+同时需要在 `serverless.yml` 的 `apigatewayConf` 中配置 `isBase64Encoded` 为 `true`。
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..225e894
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,172 @@
+[](http://serverless.com)
+
+
+
+**腾讯云 Express 组件** ⎯⎯⎯ 通过使用 [Tencent Serverless Framework](https://github.com/serverless/components/tree/cloud),基于云上 Serverless 服务(如网关、云函数等),实现“0”配置,便捷开发,极速部署你的 Express 应用,Express 组件支持丰富的配置扩展,提供了目前最易用、低成本并且弹性伸缩的 Express 项目开发/托管能力。
+
+
+特性介绍:
+
+- [x] **按需付费** - 按照请求的使用量进行收费,没有请求时无需付费
+- [x] **"0"配置** - 只需要关心项目代码,之后部署即可,Serverless Framework 会搞定所有配置。
+- [x] **极速部署** - 仅需几秒,部署你的整个 Express 应用。
+- [x] **实时日志** - 通过实时日志的输出查看业务状态,便于直接在云端开发应用。
+- [x] **云端调试** - 针对 Node.js 框架支持一键云端调试能力,屏蔽本地环境的差异。
+- [x] **便捷协作** - 通过云端的状态信息和部署日志,方便的进行多人协作开发。
+- [x] **自定义域名** - 支持配置自定义域名及 HTTPS 访问
+
+
+
+
+
+快速开始:
+
+1. [**安装**](#1-安装)
+2. [**创建**](#2-创建)
+3. [**部署**](#3-部署)
+4. [**配置**](#4-配置)
+5. [**开发调试**](#5-开发调试)
+6. [**查看状态**](#6-查看状态)
+7. [**移除**](#7-移除)
+
+更多资源:
+
+- [**架构说明**](#架构说明)
+- [**账号配置**](#账号配置)
+
+
+
+### 1. 安装
+
+通过 npm 安装最新版本的 Serverless Framework
+
+```bash
+$ npm install -g serverless
+```
+
+### 2. 创建
+
+通过如下命令和模板链接,快速创建一个 Express 应用:
+
+```bash
+$ serverless create --template-url https://github.com/serverless-components/tencent-express/tree/master/example
+$ cd example
+```
+
+执行如下命令,安装 Express 应用的对应依赖
+
+```
+$ npm install
+```
+
+### 3. 部署
+
+在 `serverless.yml` 文件下的目录中运行 `serverless deploy` 进行 Express 项目的部署。第一次部署可能耗时相对较久,但后续的二次部署会在几秒钟之内完成。部署完毕后,你可以在命令行的输出中查看到你 Express 应用的 URL 地址,点击地址即可访问你的 Express 项目。
+
+**注意:**
+
+如您的账号未[登陆](https://cloud.tencent.com/login)或[注册](https://cloud.tencent.com/register)腾讯云,您可以直接通过`微信`扫描命令行中的二维码进行授权登陆和注册。
+
+如果出现了 `internal server error` 的报错,请检查是否在创建模板后没有运行 `npm install`。
+
+如果希望查看更多部署过程的信息,可以通过`sls deploy --debug` 命令查看部署过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。
+
+
+
+### 4. 配置
+
+Express 组件支持 0 配置部署,也就是可以直接通过配置文件中的默认值进行部署。但你依然可以修改更多可选配置来进一步开发该 Express 项目。
+
+以下是 Express 组件的 `serverless.yml`完整配置说明:
+
+```yml
+# serverless.yml
+
+component: express # (required) name of the component. In that case, it's express.
+name: expressDemo # (required) name of your express component instance.
+org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup.
+app: appDemo # (optional) serverless dashboard app. default is the same as the name property.
+stage: dev # (optional) serverless dashboard stage. default is dev.
+
+inputs:
+ src:
+ src: ./ # (optional) path to the source folder. default is a hello world app.
+ exclude:
+ - .env
+ functionName: expressDemo
+ region: ap-guangzhou
+ runtime: Nodejs10.15
+ apigatewayConf:
+ protocols:
+ - http
+ - https
+ environment: release
+```
+
+点此查看[全量配置及配置说明](https://github.com/serverless-components/tencent-express/tree/master/docs/configure.md)
+
+当你根据该配置文件更新配置字段后,再次运行 `serverless deploy` 或者 `serverless` 就可以更新配置到云端。
+
+### 5. 开发调试
+
+部署了 Express.js 应用后,可以通过开发调试能力对该项目进行二次开发,从而开发一个生产应用。在本地修改和更新代码后,不需要每次都运行 `serverless deploy` 命令来反复部署。你可以直接通过 `serverless dev` 命令对本地代码的改动进行检测和自动上传。
+
+可以通过在 `serverless.yml`文件所在的目录下运行 `serverless dev` 命令开启开发调试能力。
+
+`serverless dev` 同时支持实时输出云端日志,每次部署完毕后,对项目进行访问,即可在命令行中实时输出调用日志,便于查看业务情况和排障。
+
+除了实时日志输出之外,针对 Node.js 应用,当前也支持云端调试能力。在开启 `serverless dev` 命令之后,将会自动监听远端端口,并将函数的超时时间临时配置为 900s。此时你可以通过访问 chrome://inspect/#devices 查找远端的调试路径,并直接对云端代码进行断点等调试。在调试模式结束后,需要再次部署从而将代码更新并将超时时间设置为原来的值。详情参考[开发模式和云端调试](https://cloud.tencent.com/document/product/1154/43220)。
+
+### 6. 查看状态
+
+在`serverless.yml`文件所在的目录下,通过如下命令查看部署状态:
+
+```
+$ serverless info
+```
+
+### 7. 移除
+
+在`serverless.yml`文件所在的目录下,通过以下命令移除部署的 Express 服务。移除后该组件会对应删除云上部署时所创建的所有相关资源。
+
+```
+$ serverless remove
+```
+
+和部署类似,支持通过 `sls remove --debug` 命令查看移除过程中的实时日志信息,`sls`是 `serverless` 命令的缩写。
+
+## 架构说明
+
+Express 组件将在腾讯云账户中使用到如下 Serverless 服务:
+
+- [x] **API 网关** - API 网关将会接收外部请求并且转发到 SCF 云函数中。
+- [x] **SCF 云函数** - 云函数将承载 Express.js 应用。
+- [x] **CAM 访问控制** - 该组件会创建默认 CAM 角色用于授权访问关联资源。
+- [x] **COS 对象存储** - 为确保上传速度和质量,云函数压缩并上传代码时,会默认将代码包存储在特定命名的 COS 桶中。
+- [x] **SSL 证书服务** - 如果你在 yaml 文件中配置了 `apigatewayConf.customDomains` 字段,需要做自定义域名绑定并开启 HTTPS 时,也会用到证书管理服务和域名服务。Serverless Framework 会根据已经备案的域名自动申请并配置 SSL 证书。
+
+## 账号配置
+
+当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件
+
+```console
+$ touch .env # 腾讯云的配置信息
+```
+
+在 `.env` 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存
+
+如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。
+
+如果已有腾讯云账号,可以在[API 密钥管理](https://console.cloud.tencent.com/cam/capi)中获取 `SecretId` 和`SecretKey`.
+
+```
+# .env
+TENCENT_SECRET_ID=123
+TENCENT_SECRET_KEY=123
+```
+
+## License
+
+MIT License
+
+Copyright (c) 2020 Tencent Cloud, Inc.
diff --git a/example/serverless.yml b/example/serverless.yml
index 9eae45b..fe13841 100644
--- a/example/serverless.yml
+++ b/example/serverless.yml
@@ -1,18 +1,15 @@
-org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup.
-app: appDemo # (optional) serverless dashboard app. default is the same as the name property.
-stage: dev # (optional) serverless dashboard stage. default is dev.
-component: express # (required) name of the component. In that case, it's express.
-name: expressDemo # (required) name of your express component instance.
+org: orgDemo
+app: appDemo
+stage: dev
+component: express
+name: expressDemo
inputs:
src:
- src: ./ # (optional) path to the source folder. default is a hello world app.
+ src: ./
exclude:
- .env
- region: ap-guangzhou
- runtime: Nodejs10.15
apigatewayConf:
protocols:
- http
- https
- environment: release
diff --git a/example/sls.js b/example/sls.js
index ce89135..b0d9090 100644
--- a/example/sls.js
+++ b/example/sls.js
@@ -1,6 +1,7 @@
const express = require('express')
const path = require('path')
const app = express()
+const isServerless = process.env.SERVERLESS
// Routes
app.get(`/`, (req, res) => {
@@ -39,6 +40,10 @@ app.use(function(err, req, res, next) {
res.status(500).send('Internal Serverless Error')
})
-app.listen(8080)
-
-module.exports = app
+if (isServerless) {
+ module.exports = app
+} else {
+ app.listen(3000, () => {
+ console.log(`Server start on http://localhost:3000`)
+ })
+}
diff --git a/example/sls.upload.js b/example/sls.upload.js
new file mode 100644
index 0000000..14b8552
--- /dev/null
+++ b/example/sls.upload.js
@@ -0,0 +1,59 @@
+const multer = require('multer')
+const express = require('express')
+const path = require('path')
+
+const app = express()
+const isServerless = process.env.SERVERLESS
+const upload = multer({ dest: isServerless ? '/tmp/upload' : './upload' })
+
+// Routes
+app.post('/upload', upload.single('file'), (req, res) => {
+ res.send({
+ success: true,
+ data: req.file
+ })
+})
+
+app.get(`/`, (req, res) => {
+ res.sendFile(path.join(__dirname, 'index.html'))
+})
+
+app.get('/user', (req, res) => {
+ res.send([
+ {
+ title: 'serverless framework',
+ link: 'https://serverless.com'
+ }
+ ])
+})
+
+app.get('/user/:id', (req, res) => {
+ const id = req.params.id
+ res.send({
+ id: id,
+ title: 'serverless framework',
+ link: 'https://serverless.com'
+ })
+})
+
+app.get('/404', (req, res) => {
+ res.status(404).send('Not found')
+})
+
+app.get('/500', (req, res) => {
+ res.status(500).send('Server Error')
+})
+
+// Error handler
+app.use(function(err, req, res, next) {
+ console.error(err)
+ res.status(500).send('Internal Serverless Error')
+})
+
+if (isServerless) {
+ module.exports = app
+} else {
+ app.listen(3000, () => {
+ console.log(`Server start on http://localhost:3000`)
+ })
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..a70dd57
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,14 @@
+const { join } = require('path')
+require('dotenv').config({ path: join(__dirname, '.env.test') })
+
+const config = {
+ verbose: true,
+ silent: false,
+ testTimeout: 600000,
+ testEnvironment: 'node',
+ testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
+ testPathIgnorePatterns: ['/node_modules/', '/__tests__/lib/'],
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
+}
+
+module.exports = config
diff --git a/package.json b/package.json
index b06f08f..4b0d707 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,11 @@
{
"name": "@serverless/express",
- "version": "0.0.17",
"main": "src/serverless.js",
"publishConfig": {
"access": "public"
},
"scripts": {
- "test": "npm run lint && npm run prettier",
+ "test": "jest",
"commitlint": "commitlint -f HEAD@{15}",
"lint": "eslint --ext .js,.ts,.tsx .",
"lint:fix": "eslint --fix --ext .js,.ts,.tsx .",
@@ -18,9 +17,9 @@
},
"husky": {
"hooks": {
- "pre-commit": "lint-staged",
+ "pre-commit": "ygsec && lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
- "pre-push": "npm run lint:fix && npm run prettier:fix"
+ "pre-push": "ygsec && npm run lint:fix && npm run prettier:fix"
}
},
"lint-staged": {
@@ -44,15 +43,41 @@
"@semantic-release/git": "^9.0.0",
"@semantic-release/npm": "^7.0.4",
"@semantic-release/release-notes-generator": "^9.0.1",
+ "@serverless/platform-client-china": "^1.0.19",
+ "@ygkit/secure": "0.0.3",
+ "axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.2",
- "husky": "^4.2.3",
+ "husky": "^4.2.5",
+ "jest": "^25.0.1",
"lint-staged": "^10.0.8",
"prettier": "^1.19.1",
"semantic-release": "^17.0.4"
- }
+ },
+ "description": "Easily deploy serverless Express.js applications to Tencent Cloud with the Serverless Framework",
+ "directories": {
+ "doc": "docs",
+ "example": "example",
+ "test": "tests"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/serverless-components/tencent-express.git"
+ },
+ "keywords": [
+ "serverless-express",
+ "serverless",
+ "express",
+ "serverless-framework",
+ "serverless-components",
+ "tencent-cloud"
+ ],
+ "bugs": {
+ "url": "https://github.com/serverless-components/tencent-express/issues"
+ },
+ "homepage": "https://github.com/serverless-components/tencent-express#readme"
}
diff --git a/release.config.js b/release.config.js
index 53f3398..98b3864 100644
--- a/release.config.js
+++ b/release.config.js
@@ -1,7 +1,6 @@
module.exports = {
verifyConditions: [
'@semantic-release/changelog',
- '@semantic-release/npm',
'@semantic-release/git',
'@semantic-release/github'
],
@@ -33,14 +32,6 @@ module.exports = {
changelogFile: 'CHANGELOG.md'
}
],
- [
- '@semantic-release/npm',
- {
- pkgRoot: '.',
- npmPublish: false,
- tarballDir: false
- }
- ],
[
'@semantic-release/git',
{
diff --git a/serverless.component.yml b/serverless.component.yml
index c9a84d4..34269df 100644
--- a/serverless.component.yml
+++ b/serverless.component.yml
@@ -1,10 +1,11 @@
name: express
-version: 0.0.17
+version: 0.3.0
author: Tencent Cloud, Inc.
org: Tencent Cloud, Inc.
-description: Deploys a serverless Express.js application onto Tencent SCF and Tencent APIGateway.
+description: Deploy a serverless Express.js application on Tencent SCF and API Gateway.
keywords: tencent, serverless, express
-repo: https://github.com/serverless-components/tencent-express/tree/v2
-readme: https://github.com/serverless-components/tencent-express/tree/v2/README.md
+repo: https://github.com/serverless-components/tencent-express
+readme: https://github.com/serverless-components/tencent-express/tree/master/README.md
license: MIT
main: ./src
+webDeployable: true
diff --git a/src/_shims/handler.js b/src/_shims/handler.js
index f132ec2..5863add 100644
--- a/src/_shims/handler.js
+++ b/src/_shims/handler.js
@@ -4,15 +4,15 @@ const path = require('path')
const { createServer, proxy } = require('tencent-serverless-http')
let server
+let app
exports.handler = async (event, context) => {
- const userSls = path.join(__dirname, '..', 'sls.js')
- let app
+ const userSls = path.join(__dirname, '..', process.env.SLS_ENTRY_FILE)
if (fs.existsSync(userSls)) {
- // load the user provided app
+ // eslint-disable-next-line
+ console.log(`Using user custom entry file ${process.env.SLS_ENTRY_FILE}`)
app = require(userSls)
} else {
- // load the built-in default app
app = require('./sls.js')
}
@@ -20,6 +20,11 @@ exports.handler = async (event, context) => {
app.request.__SLS_EVENT__ = event
app.request.__SLS_CONTEXT__ = context
+ // provide sls intialize hooks
+ if (app.slsInitialize && typeof app.slsInitialize === 'function') {
+ await app.slsInitialize()
+ }
+
// cache server, not create repeatly
if (!server) {
server = createServer(app, null, app.binaryTypes || [])
@@ -27,11 +32,6 @@ exports.handler = async (event, context) => {
context.callbackWaitsForEmptyEventLoop = app.callbackWaitsForEmptyEventLoop === true
- // provide sls intialize hooks
- if (app.slsInitialize && typeof app.slsInitialize === 'function') {
- await app.slsInitialize()
- }
-
const result = await proxy(server, event, context, 'PROMISE')
return result.promise
}
diff --git a/src/config.js b/src/config.js
index 51f414a..35901dd 100644
--- a/src/config.js
+++ b/src/config.js
@@ -3,13 +3,13 @@ const CONFIGS = {
'https://serverless-templates-1300862921.cos.ap-beijing.myqcloud.com/express-demo.zip',
compName: 'express',
compFullname: 'Express.js',
+ defaultEntryFile: 'sls.js',
handler: 'sl_handler.handler',
runtime: 'Nodejs10.15',
- exclude: ['.git/**', '.gitignore', '.DS_Store'],
timeout: 3,
memorySize: 128,
namespace: 'default',
- description: 'This is a function created by serverless component'
+ description: 'Created by Serverless Component'
}
module.exports = CONFIGS
diff --git a/src/package.json b/src/package.json
index f99537f..8722896 100644
--- a/src/package.json
+++ b/src/package.json
@@ -1,18 +1,7 @@
{
- "name": "@serverless/express",
- "main": "./serverless.js",
- "publishConfig": {
- "access": "public"
- },
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "lint": "eslint . --fix --cache"
- },
- "author": "Tencent Cloud, Inc.",
- "license": "MIT",
"dependencies": {
"download": "^8.0.0",
- "tencent-component-toolkit": "^1.12.7",
+ "tencent-component-toolkit": "^1.20.10",
"type": "^2.0.0"
}
}
diff --git a/src/serverless.js b/src/serverless.js
index 5b69ee9..f76e037 100644
--- a/src/serverless.js
+++ b/src/serverless.js
@@ -1,7 +1,7 @@
const { Component } = require('@serverless/core')
-const { MultiApigw, Scf, Apigw, Cns, Cam, Metrics } = require('tencent-component-toolkit')
+const { Scf, Apigw, Cns, Cam, Metrics } = require('tencent-component-toolkit')
const { TypeError } = require('tencent-component-toolkit/src/utils/error')
-const { uploadCodeToCos, getDefaultProtocol, deleteRecord, prepareInputs } = require('./utils')
+const { uploadCodeToCos, getDefaultProtocol, prepareInputs, deepClone } = require('./utils')
const CONFIGS = require('./config')
class ServerlessComponent extends Component {
@@ -39,134 +39,141 @@ class ServerlessComponent extends Component {
}
}
- const uploadCodeHandler = []
const outputs = {}
const appId = this.getAppId()
- for (let eveRegionIndex = 0; eveRegionIndex < regionList.length; eveRegionIndex++) {
- const curRegion = regionList[eveRegionIndex]
- const funcDeployer = async () => {
- const code = await uploadCodeToCos(this, appId, credentials, inputs, curRegion)
- const scf = new Scf(credentials, curRegion)
- const tempInputs = {
- ...inputs,
- code
- }
- const scfOutput = await scf.deploy(tempInputs)
- outputs[curRegion] = {
- functionName: scfOutput.FunctionName,
- runtime: scfOutput.Runtime,
- namespace: scfOutput.Namespace
- }
+ const funcDeployer = async (curRegion) => {
+ const code = await uploadCodeToCos(this, appId, credentials, inputs, curRegion)
+ const scf = new Scf(credentials, curRegion)
+ const tempInputs = {
+ ...inputs,
+ code
+ }
+ const scfOutput = await scf.deploy(deepClone(tempInputs))
+ outputs[curRegion] = {
+ functionName: scfOutput.FunctionName,
+ runtime: scfOutput.Runtime,
+ namespace: scfOutput.Namespace
+ }
- this.state[curRegion] = {
- ...(this.state[curRegion] ? this.state[curRegion] : {}),
- ...outputs[curRegion]
- }
+ this.state[curRegion] = {
+ ...(this.state[curRegion] ? this.state[curRegion] : {}),
+ ...outputs[curRegion]
+ }
- // default version is $LATEST
- outputs[curRegion].lastVersion = scfOutput.LastVersion
- ? scfOutput.LastVersion
- : this.state.lastVersion || '$LATEST'
+ // default version is $LATEST
+ outputs[curRegion].lastVersion = scfOutput.LastVersion
+ ? scfOutput.LastVersion
+ : this.state.lastVersion || '$LATEST'
- // default traffic is 1.0, it can also be 0, so we should compare to undefined
- outputs[curRegion].traffic = scfOutput.Traffic
+ // default traffic is 1.0, it can also be 0, so we should compare to undefined
+ outputs[curRegion].traffic =
+ scfOutput.Traffic !== undefined
? scfOutput.Traffic
: this.state.traffic !== undefined
? this.state.traffic
: 1
- if (outputs[curRegion].traffic !== 1 && scfOutput.ConfigTrafficVersion) {
- outputs[curRegion].configTrafficVersion = scfOutput.ConfigTrafficVersion
- this.state.configTrafficVersion = scfOutput.ConfigTrafficVersion
- }
-
- this.state.lastVersion = outputs[curRegion].lastVersion
- this.state.traffic = outputs[curRegion].traffic
+ if (outputs[curRegion].traffic !== 1 && scfOutput.ConfigTrafficVersion) {
+ outputs[curRegion].configTrafficVersion = scfOutput.ConfigTrafficVersion
+ this.state.configTrafficVersion = scfOutput.ConfigTrafficVersion
}
- uploadCodeHandler.push(funcDeployer())
+
+ this.state.lastVersion = outputs[curRegion].lastVersion
+ this.state.traffic = outputs[curRegion].traffic
+ }
+
+ for (let i = 0; i < regionList.length; i++) {
+ const curRegion = regionList[i]
+ await funcDeployer(curRegion)
}
- await Promise.all(uploadCodeHandler)
this.save()
return outputs
}
+ // try to add dns record
+ async tryToAddDnsRecord(credentials, customDomains) {
+ try {
+ const cns = new Cns(credentials)
+ for (let i = 0; i < customDomains.length; i++) {
+ const item = customDomains[i]
+ if (item.domainPrefix) {
+ await cns.deploy({
+ domain: item.subDomain.replace(`${item.domainPrefix}.`, ''),
+ records: [
+ {
+ subDomain: item.domainPrefix,
+ recordType: 'CNAME',
+ recordLine: '默认',
+ value: item.cname,
+ ttl: 600,
+ mx: 10,
+ status: 'enable'
+ }
+ ]
+ })
+ }
+ }
+ } catch (e) {
+ console.log('METHOD_tryToAddDnsRecord', e.message)
+ }
+ }
+
async deployApigateway(credentials, inputs, regionList) {
if (inputs.isDisabled) {
return {}
}
- const apigw = new MultiApigw(credentials, regionList)
- const oldState = this.state[regionList[0]] || {}
- inputs.oldState = {
- apiList: oldState.apiList || [],
- customDomains: oldState.customDomains || []
+
+ const getServiceId = (instance, region) => {
+ const regionState = instance.state[region]
+ return inputs.serviceId || (regionState && regionState.serviceId)
}
- const apigwOutputs = await apigw.deploy(inputs)
- const outputs = {}
- Object.keys(apigwOutputs).forEach((curRegion) => {
- const curOutput = apigwOutputs[curRegion]
- outputs[curRegion] = {
- serviceId: curOutput.serviceId,
- subDomain: curOutput.subDomain,
- environment: curOutput.environment,
- url: `${getDefaultProtocol(inputs.protocols)}://${curOutput.subDomain}/${
- curOutput.environment
- }/`
- }
- if (curOutput.customDomains) {
- outputs[curRegion].customDomains = curOutput.customDomains
- }
- this.state[curRegion] = {
- created: curOutput.created,
- ...(this.state[curRegion] ? this.state[curRegion] : {}),
- ...outputs[curRegion],
- apiList: curOutput.apiList
- }
- })
- this.save()
- return outputs
- }
- async deployCns(credentials, inputs, regionList, apigwOutputs) {
- const cns = new Cns(credentials)
- const cnsRegion = {}
+ const deployTasks = []
+ const outputs = {}
regionList.forEach((curRegion) => {
- const curApigwOutput = apigwOutputs[curRegion]
- cnsRegion[curRegion] = curApigwOutput.subDomain
- })
+ const apigwDeployer = async () => {
+ const apigw = new Apigw(credentials, curRegion)
- const state = []
- const outputs = {}
- const tempJson = {}
- for (let i = 0; i < inputs.length; i++) {
- const curCns = inputs[i]
- for (let j = 0; j < curCns.records.length; j++) {
- curCns.records[j].value =
- cnsRegion[curCns.records[j].value.replace('temp_value_about_', '')]
- }
- const tencentCnsOutputs = await cns.deploy(curCns)
- outputs[curCns.domain] = tencentCnsOutputs.DNS
- ? tencentCnsOutputs.DNS
- : 'The domain name has already been added.'
- tencentCnsOutputs.domain = curCns.domain
- state.push(tencentCnsOutputs)
- }
+ const oldState = this.state[curRegion] || {}
+ const apigwInputs = {
+ ...inputs,
+ oldState: {
+ apiList: oldState.apiList || [],
+ customDomains: oldState.customDomains || []
+ }
+ }
+ // different region deployment has different service id
+ apigwInputs.serviceId = getServiceId(this, curRegion)
+ const apigwOutput = await apigw.deploy(deepClone(apigwInputs))
+ outputs[curRegion] = {
+ serviceId: apigwOutput.serviceId,
+ subDomain: apigwOutput.subDomain,
+ environment: apigwOutput.environment,
+ url: `${getDefaultProtocol(inputs.protocols)}://${apigwOutput.subDomain}/${
+ apigwOutput.environment
+ }${apigwInputs.endpoints[0].path}`
+ }
- // 删除serverless创建的但是不在本次列表中
- try {
- for (let i = 0; i < state.length; i++) {
- tempJson[state[i].domain] = state[i].records
- }
- const recordHistory = this.state.cns || []
- for (let i = 0; i < recordHistory.length; i++) {
- const delList = deleteRecord(tempJson[recordHistory[i].domain], recordHistory[i].records)
- if (delList && delList.length > 0) {
- await cns.remove({ deleteList: delList })
+ if (apigwOutput.customDomains) {
+ // TODO: need confirm add cns authentication
+ if (inputs.autoAddDnsRecord === true) {
+ // await this.tryToAddDnsRecord(credentials, apigwOutput.customDomains)
+ }
+ outputs[curRegion].customDomains = apigwOutput.customDomains
+ }
+ this.state[curRegion] = {
+ created: true,
+ ...(this.state[curRegion] ? this.state[curRegion] : {}),
+ ...outputs[curRegion],
+ apiList: apigwOutput.apiList
}
}
- } catch (e) {}
+ deployTasks.push(apigwDeployer())
+ })
+
+ await Promise.all(deployTasks)
- this.state['cns'] = state
this.save()
return outputs
}
@@ -177,7 +184,7 @@ class ServerlessComponent extends Component {
const credentials = this.getCredentials()
// 对Inputs内容进行标准化
- const { regionList, functionConf, apigatewayConf, cnsConf } = await prepareInputs(
+ const { regionList, functionConf, apigatewayConf } = await prepareInputs(
this,
credentials,
inputs
@@ -189,29 +196,33 @@ class ServerlessComponent extends Component {
outputs.templateUrl = CONFIGS.templateUrl
}
- const deployTasks = [this.deployFunction(credentials, functionConf, regionList, outputs)]
+ let apigwOutputs
+ const functionOutputs = await this.deployFunction(
+ credentials,
+ functionConf,
+ regionList,
+ outputs
+ )
// support apigatewayConf.isDisabled
if (apigatewayConf.isDisabled !== true) {
- deployTasks.push(this.deployApigateway(credentials, apigatewayConf, regionList, outputs))
+ apigwOutputs = await this.deployApigateway(credentials, apigatewayConf, regionList, outputs)
} else {
this.state.apigwDisabled = true
}
- const [functionOutputs, apigwOutputs = {}] = await Promise.all(deployTasks)
// optimize outputs for one region
if (regionList.length === 1) {
const [oneRegion] = regionList
outputs.region = oneRegion
- outputs['apigw'] = apigwOutputs[oneRegion]
outputs['scf'] = functionOutputs[oneRegion]
+ if (apigwOutputs) {
+ outputs['apigw'] = apigwOutputs[oneRegion]
+ }
} else {
- outputs['apigw'] = apigwOutputs
outputs['scf'] = functionOutputs
- }
-
- // cns depends on apigw, so if disabled apigw, just ignore it.
- if (cnsConf.length > 0 && apigatewayConf.isDisabled !== true) {
- outputs['cns'] = await this.deployCns(credentials, cnsConf, regionList, apigwOutputs)
+ if (apigwOutputs) {
+ outputs['apigw'] = apigwOutputs
+ }
}
this.state.region = regionList[0]
@@ -236,10 +247,6 @@ class ServerlessComponent extends Component {
const scf = new Scf(credentials, curRegion)
const apigw = new Apigw(credentials, curRegion)
const handler = async () => {
- await scf.remove({
- functionName: curState.functionName,
- namespace: curState.namespace
- })
// if disable apigw, no need to remove
if (state.apigwDisabled !== true) {
await apigw.remove({
@@ -250,6 +257,10 @@ class ServerlessComponent extends Component {
customDomains: curState.customDomains
})
}
+ await scf.remove({
+ functionName: curState.functionName,
+ namespace: curState.namespace
+ })
}
removeHandlers.push(handler())
}
@@ -290,6 +301,12 @@ class ServerlessComponent extends Component {
region,
timezone: inputs.tz
}
+
+ const curState = this.state[region]
+ if (curState.serviceId) {
+ options.apigwServiceId = curState.serviceId
+ options.apigwEnvironment = curState.environment || 'release'
+ }
const credentials = this.getCredentials()
const mertics = new Metrics(credentials, options)
const metricResults = await mertics.getDatas(
diff --git a/src/utils.js b/src/utils.js
index 5403265..b8b6b6d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,5 +1,5 @@
const path = require('path')
-const { Domain, Cos } = require('tencent-component-toolkit')
+const { Cos } = require('tencent-component-toolkit')
const ensureObject = require('type/object/ensure')
const ensureIterable = require('type/iterable/ensure')
const ensureString = require('type/string/ensure')
@@ -15,10 +15,45 @@ const generateId = () =>
.toString(36)
.substring(6)
+const deepClone = (obj) => {
+ return JSON.parse(JSON.stringify(obj))
+}
+
const getType = (obj) => {
return Object.prototype.toString.call(obj).slice(8, -1)
}
+const mergeJson = (sourceJson, targetJson) => {
+ Object.entries(sourceJson).forEach(([key, val]) => {
+ targetJson[key] = deepClone(val)
+ })
+ return targetJson
+}
+
+const capitalString = (str) => {
+ if (str.length < 2) {
+ return str.toUpperCase()
+ }
+
+ return `${str[0].toUpperCase()}${str.slice(1)}`
+}
+
+const getDefaultProtocol = (protocols) => {
+ return String(protocols).includes('https') ? 'https' : 'http'
+}
+
+const getDefaultFunctionName = () => {
+ return `${CONFIGS.compName}_component_${generateId()}`
+}
+
+const getDefaultServiceName = () => {
+ return 'serverless'
+}
+
+const getDefaultServiceDescription = () => {
+ return 'Created by Serverless Component'
+}
+
const validateTraffic = (num) => {
if (getType(num) !== 'Number') {
throw new TypeError(
@@ -107,12 +142,18 @@ const uploadCodeToCos = async (instance, appId, credentials, inputs, region) =>
object: objectName,
method: 'PUT'
})
- const slsSDKEntries = instance.getSDKEntries('_shims/handler.handler')
+ // if shims and sls sdk entries had been injected to zipPath, no need to injected again
console.log(`Uploading code to bucket ${bucketName}`)
- await instance.uploadSourceZipToCOS(zipPath, uploadUrl, slsSDKEntries, {
- _shims: path.join(__dirname, '_shims')
- })
+ if (instance.codeInjected === true) {
+ await instance.uploadSourceZipToCOS(zipPath, uploadUrl, {}, {})
+ } else {
+ const slsSDKEntries = instance.getSDKEntries('_shims/handler.handler')
+ await instance.uploadSourceZipToCOS(zipPath, uploadUrl, slsSDKEntries, {
+ _shims: path.join(__dirname, '_shims')
+ })
+ instance.codeInjected = true
+ }
console.log(`Upload ${objectName} to bucket ${bucketName} success`)
}
}
@@ -127,72 +168,13 @@ const uploadCodeToCos = async (instance, appId, credentials, inputs, region) =>
}
}
-const mergeJson = (sourceJson, targetJson) => {
- for (const eveKey in sourceJson) {
- if (targetJson.hasOwnProperty(eveKey)) {
- if (['protocols', 'endpoints', 'customDomain'].indexOf(eveKey) != -1) {
- for (let i = 0; i < sourceJson[eveKey].length; i++) {
- const sourceEvents = JSON.stringify(sourceJson[eveKey][i])
- const targetEvents = JSON.stringify(targetJson[eveKey])
- if (targetEvents.indexOf(sourceEvents) == -1) {
- targetJson[eveKey].push(sourceJson[eveKey][i])
- }
- }
- } else {
- if (typeof sourceJson[eveKey] != 'string') {
- mergeJson(sourceJson[eveKey], targetJson[eveKey])
- } else {
- targetJson[eveKey] = sourceJson[eveKey]
- }
- }
- } else {
- targetJson[eveKey] = sourceJson[eveKey]
- }
- }
- return targetJson
-}
-
-const capitalString = (str) => {
- if (str.length < 2) {
- return str.toUpperCase()
- }
-
- return `${str[0].toUpperCase()}${str.slice(1)}`
-}
-
-const getDefaultProtocol = (protocols) => {
- if (protocols.map((i) => i.toLowerCase()).includes('https')) {
- return 'https'
- }
- return 'http'
-}
-
-const deleteRecord = (newRecords, historyRcords) => {
- const deleteList = []
- for (let i = 0; i < historyRcords.length; i++) {
- let temp = false
- for (let j = 0; j < newRecords.length; j++) {
- if (
- newRecords[j].domain == historyRcords[i].domain &&
- newRecords[j].subDomain == historyRcords[i].subDomain &&
- newRecords[j].recordType == historyRcords[i].recordType &&
- newRecords[j].value == historyRcords[i].value &&
- newRecords[j].recordLine == historyRcords[i].recordLine
- ) {
- temp = true
- break
- }
- }
- if (!temp) {
- deleteList.push(historyRcords[i])
- }
- }
- return deleteList
-}
-
const prepareInputs = async (instance, credentials, inputs = {}) => {
// 对function inputs进行标准化
- const tempFunctionConf = inputs.functionConf ? inputs.functionConf : {}
+ const tempFunctionConf = inputs.functionConf
+ ? inputs.functionConf
+ : inputs.functionConfig
+ ? inputs.functionConfig
+ : {}
const fromClientRemark = `tencent-${CONFIGS.compName}`
const regionList = inputs.region
? typeof inputs.region == 'string'
@@ -203,10 +185,7 @@ const prepareInputs = async (instance, credentials, inputs = {}) => {
// chenck state function name
const stateFunctionName =
instance.state[regionList[0]] && instance.state[regionList[0]].functionName
- // check state service id
- const stateServiceId = instance.state[regionList[0]] && instance.state[regionList[0]].serviceId
-
- const functionConf = {
+ const functionConf = Object.assign(tempFunctionConf, {
code: {
src: inputs.src,
bucket: inputs.srcOriginal && inputs.srcOriginal.bucket,
@@ -215,7 +194,7 @@ const prepareInputs = async (instance, credentials, inputs = {}) => {
name:
ensureString(inputs.functionName, { isOptional: true }) ||
stateFunctionName ||
- `${CONFIGS.compName}_component_${generateId()}`,
+ getDefaultFunctionName(),
region: regionList,
role: ensureString(tempFunctionConf.role ? tempFunctionConf.role : inputs.role, {
default: ''
@@ -240,10 +219,18 @@ const prepareInputs = async (instance, credentials, inputs = {}) => {
layers: ensureIterable(tempFunctionConf.layers ? tempFunctionConf.layers : inputs.layers, {
default: []
}),
+ cfs: ensureIterable(tempFunctionConf.cfs ? tempFunctionConf.cfs : inputs.cfs, {
+ default: []
+ }),
publish: inputs.publish,
traffic: inputs.traffic,
- lastVersion: instance.state.lastVersion
- }
+ lastVersion: instance.state.lastVersion,
+ timeout: tempFunctionConf.timeout ? tempFunctionConf.timeout : CONFIGS.timeout,
+ memorySize: tempFunctionConf.memorySize ? tempFunctionConf.memorySize : CONFIGS.memorySize,
+ tags: ensureObject(tempFunctionConf.tags ? tempFunctionConf.tags : inputs.tag, {
+ default: null
+ })
+ })
// validate traffic
if (inputs.traffic !== undefined) {
@@ -251,82 +238,78 @@ const prepareInputs = async (instance, credentials, inputs = {}) => {
}
functionConf.needSetTraffic = inputs.traffic !== undefined && functionConf.lastVersion
- functionConf.tags = ensureObject(tempFunctionConf.tags ? tempFunctionConf.tags : inputs.tag, {
- default: null
- })
-
- functionConf.include = ensureIterable(
- tempFunctionConf.include ? tempFunctionConf.include : inputs.include,
- { default: [], ensureItem: ensureString }
- )
- functionConf.exclude = ensureIterable(
- tempFunctionConf.exclude ? tempFunctionConf.exclude : inputs.exclude,
- { default: [], ensureItem: ensureString }
- )
- functionConf.exclude.push('.git/**', '.gitignore', '.serverless', '.DS_Store')
- if (inputs.functionConf) {
- functionConf.timeout = inputs.functionConf.timeout
- ? inputs.functionConf.timeout
- : CONFIGS.timeout
- functionConf.memorySize = inputs.functionConf.memorySize
- ? inputs.functionConf.memorySize
- : CONFIGS.memorySize
- if (inputs.functionConf.environment) {
- functionConf.environment = inputs.functionConf.environment
- }
- if (inputs.functionConf.vpcConfig) {
- functionConf.vpcConfig = inputs.functionConf.vpcConfig
+ if (tempFunctionConf.environment) {
+ functionConf.environment = tempFunctionConf.environment
+ functionConf.environment.variables = functionConf.environment.variables || {}
+ functionConf.environment.variables.SERVERLESS = '1'
+ functionConf.environment.variables.SLS_ENTRY_FILE = inputs.entryFile || CONFIGS.defaultEntryFile
+ } else {
+ functionConf.environment = {
+ variables: {
+ SERVERLESS: '1',
+ SLS_ENTRY_FILE: inputs.entryFile || CONFIGS.defaultEntryFile
+ }
}
}
+ if (tempFunctionConf.vpcConfig) {
+ functionConf.vpcConfig = tempFunctionConf.vpcConfig
+ }
+
// 对apigw inputs进行标准化
- const apigatewayConf = inputs.apigatewayConf ? inputs.apigatewayConf : {}
- apigatewayConf.isDisabled = inputs.apigatewayConf === true
- apigatewayConf.fromClientRemark = fromClientRemark
- apigatewayConf.serviceName = inputs.serviceName
- apigatewayConf.description = `Serverless Framework Tencent-${capitalString(
- CONFIGS.compName
- )} Component`
- apigatewayConf.serviceId = inputs.serviceId || stateServiceId
- apigatewayConf.region = functionConf.region
- apigatewayConf.protocols = apigatewayConf.protocols || ['http']
- apigatewayConf.environment = apigatewayConf.environment ? apigatewayConf.environment : 'release'
- apigatewayConf.endpoints = [
- {
- path: '/',
- enableCORS: apigatewayConf.enableCORS,
- serviceTimeout: apigatewayConf.serviceTimeout,
- method: 'ANY',
- function: {
- isIntegratedResponse: apigatewayConf.isIntegratedResponse === false ? false : true,
- functionName: functionConf.name,
- functionNamespace: functionConf.namespace,
- functionQualifier: functionConf.needSetTraffic ? '$DEFAULT' : '$LATEST'
+ const tempApigwConf = inputs.apigatewayConf
+ ? inputs.apigatewayConf
+ : inputs.apigwConfig
+ ? inputs.apigwConfig
+ : {}
+ const apigatewayConf = Object.assign(tempApigwConf, {
+ serviceId: inputs.serviceId || tempApigwConf.serviceId,
+ region: regionList,
+ isDisabled: tempApigwConf.isDisabled === true,
+ fromClientRemark: fromClientRemark,
+ serviceName: inputs.serviceName || tempApigwConf.serviceName || getDefaultServiceName(instance),
+ serviceDesc: tempApigwConf.serviceDesc || getDefaultServiceDescription(instance),
+ protocols: tempApigwConf.protocols || ['http'],
+ environment: tempApigwConf.environment ? tempApigwConf.environment : 'release',
+ customDomains: tempApigwConf.customDomains || []
+ })
+ if (!apigatewayConf.endpoints) {
+ apigatewayConf.endpoints = [
+ {
+ path: tempApigwConf.path || '/',
+ enableCORS: tempApigwConf.enableCORS,
+ serviceTimeout: tempApigwConf.serviceTimeout,
+ method: 'ANY',
+ apiName: tempApigwConf.apiName || 'index',
+ isBase64Encoded: tempApigwConf.isBase64Encoded,
+ isBase64Trigger: tempApigwConf.isBase64Trigger,
+ base64EncodedTriggerRules: tempApigwConf.base64EncodedTriggerRules,
+ function: {
+ isIntegratedResponse: true,
+ functionName: functionConf.name,
+ functionNamespace: functionConf.namespace,
+ functionQualifier:
+ (tempApigwConf.function && tempApigwConf.function.functionQualifier) || '$LATEST'
+ }
}
- }
- ]
- if (apigatewayConf.usagePlan) {
+ ]
+ }
+ if (tempApigwConf.usagePlan) {
apigatewayConf.endpoints[0].usagePlan = {
- usagePlanId: apigatewayConf.usagePlan.usagePlanId,
- usagePlanName: apigatewayConf.usagePlan.usagePlanName,
- usagePlanDesc: apigatewayConf.usagePlan.usagePlanDesc,
- maxRequestNum: apigatewayConf.usagePlan.maxRequestNum
+ usagePlanId: tempApigwConf.usagePlan.usagePlanId,
+ usagePlanName: tempApigwConf.usagePlan.usagePlanName,
+ usagePlanDesc: tempApigwConf.usagePlan.usagePlanDesc,
+ maxRequestNum: tempApigwConf.usagePlan.maxRequestNum
}
}
- if (apigatewayConf.auth) {
+ if (tempApigwConf.auth) {
apigatewayConf.endpoints[0].auth = {
- secretName: apigatewayConf.auth.secretName,
- secretIds: apigatewayConf.auth.secretIds
+ secretName: tempApigwConf.auth.secretName,
+ secretIds: tempApigwConf.auth.secretIds
}
}
- // 对cns inputs进行标准化
- const tempCnsConf = {}
- const tempCnsBaseConf = inputs.cloudDNSConf ? inputs.cloudDNSConf : {}
-
- // 分地域处理functionConf/apigatewayConf/cnsConf
- for (let i = 0; i < functionConf.region.length; i++) {
- const curRegion = functionConf.region[i]
+ regionList.forEach((curRegion) => {
const curRegionConf = inputs[curRegion]
if (curRegionConf && curRegionConf.functionConf) {
functionConf[curRegion] = curRegionConf.functionConf
@@ -334,62 +317,21 @@ const prepareInputs = async (instance, credentials, inputs = {}) => {
if (curRegionConf && curRegionConf.apigatewayConf) {
apigatewayConf[curRegion] = curRegionConf.apigatewayConf
}
-
- const tempRegionCnsConf = mergeJson(
- tempCnsBaseConf,
- curRegionConf && curRegionConf.cloudDNSConf ? curRegionConf.cloudDNSConf : {}
- )
-
- tempCnsConf[functionConf.region[i]] = {
- recordType: 'CNAME',
- recordLine: tempRegionCnsConf.recordLine ? tempRegionCnsConf.recordLine : undefined,
- ttl: tempRegionCnsConf.ttl,
- mx: tempRegionCnsConf.mx,
- status: tempRegionCnsConf.status ? tempRegionCnsConf.status : 'enable'
- }
- }
-
- const cnsConf = []
- // 对cns inputs进行检查和赋值
- if (apigatewayConf.customDomain && apigatewayConf.customDomain.length > 0) {
- const domain = new Domain(credentials)
- for (let domianNum = 0; domianNum < apigatewayConf.customDomain.length; domianNum++) {
- const domainData = await domain.check(apigatewayConf.customDomain[domianNum].domain)
- const tempInputs = {
- domain: domainData.domain,
- records: []
- }
- for (let eveRecordNum = 0; eveRecordNum < functionConf.region.length; eveRecordNum++) {
- if (tempCnsConf[functionConf.region[eveRecordNum]].recordLine) {
- tempInputs.records.push({
- subDomain: domainData.subDomain || '@',
- recordType: 'CNAME',
- recordLine: tempCnsConf[functionConf.region[eveRecordNum]].recordLine,
- value: `temp_value_about_${functionConf.region[eveRecordNum]}`,
- ttl: tempCnsConf[functionConf.region[eveRecordNum]].ttl,
- mx: tempCnsConf[functionConf.region[eveRecordNum]].mx,
- status: tempCnsConf[functionConf.region[eveRecordNum]].status || 'enable'
- })
- }
- }
- cnsConf.push(tempInputs)
- }
- }
+ })
return {
regionList,
functionConf,
- apigatewayConf,
- cnsConf
+ apigatewayConf
}
}
module.exports = {
+ deepClone,
generateId,
uploadCodeToCos,
mergeJson,
capitalString,
getDefaultProtocol,
- deleteRecord,
prepareInputs
}