├── .commitlintrc.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODE_OF_CONDUCT.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── lint-and-test-code.yml │ └── publish-to-npm-alpha.yml ├── .gitignore ├── .releaserc.json ├── LICENSE ├── README.md ├── benchmark ├── Makefile ├── README.md ├── originServer.js ├── package-lock.json ├── package.json ├── run └── start │ ├── start.js │ ├── startByTsw.js │ ├── startByTswNoReport.js │ ├── tswconfig.js │ └── tswconfigNoReport.js ├── changeLog.md ├── docs └── use-open-platform.md ├── examples ├── https │ ├── README.md │ ├── index.js │ ├── package.json │ ├── tswconfig.js │ └── yarn.lock └── koa │ ├── README.md │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── tswconfig.js │ └── yarn.lock ├── jest.config.js ├── lib ├── __test__ │ ├── __fixtures__ │ │ ├── error-plugin │ │ │ ├── index.ts │ │ │ ├── plugin.ts │ │ │ └── tswconfig.ts │ │ ├── no-plugin │ │ │ ├── index.ts │ │ │ └── tswconfig.ts │ │ └── normal-plugin │ │ │ ├── index.ts │ │ │ ├── plugin.ts │ │ │ └── tswconfig.ts │ ├── cli.test.ts │ └── index.test.ts ├── cli.ts ├── core │ ├── __test__ │ │ └── winston.test.ts │ ├── bus.ts │ ├── config.ts │ ├── context.ts │ ├── logger │ │ ├── __test__ │ │ │ ├── callinfo.test.ts │ │ │ └── index.test.ts │ │ ├── callInfo.ts │ │ └── index.ts │ ├── runtime │ │ ├── __test__ │ │ │ ├── console.hack.test.ts │ │ │ ├── create-server.test.ts │ │ │ └── dns.hack.test.ts │ │ ├── capture │ │ │ ├── __test__ │ │ │ │ ├── incoming.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ └── outgoing.test.ts │ │ │ ├── incoming.ts │ │ │ ├── index.ts │ │ │ └── outgoing.ts │ │ ├── console.hack.ts │ │ ├── create-server.hack.ts │ │ └── dns.hack.ts │ ├── util │ │ ├── __test__ │ │ │ └── isInspect.test.ts │ │ ├── isInspect.ts │ │ └── isLinux.ts │ └── winston.ts └── index.ts ├── package.json ├── patches └── jest-util+26.6.2.patch ├── static └── images │ ├── appid-appkey.png │ ├── capture.png │ ├── create-app.png │ ├── log-view.png │ ├── log.png │ ├── user │ ├── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ ├── 05.png │ ├── 06.png │ ├── 07.png │ ├── 08.png │ ├── 09.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 20.png │ ├── 21.png │ ├── 22.png │ ├── 23.png │ ├── 24.png │ ├── 25.png │ ├── 26.png │ └── 27.png │ └── winston-log.png ├── tsconfig.json ├── typings └── type.d.ts └── yarn.lock /.commitlintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - "@commitlint/config-conventional" 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | 17 | [*.yml] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples 2 | node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb-base", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "tsconfig.json" 9 | }, 10 | "plugins": [ 11 | "@typescript-eslint", 12 | "eslint-plugin-import", 13 | "eslint-plugin-eslint-comments", 14 | "eslint-plugin-jsdoc" 15 | ], 16 | "env": { 17 | "node": true, 18 | "jest": true 19 | }, 20 | "rules": { 21 | "no-console": "off", 22 | 23 | // 有大量对未暴露出来的属性做 hack 24 | // 需要使用 (res as any)._send = () => {} 的方式 25 | "no-underscore-dangle": "off", 26 | "@typescript-eslint/no-explicit-any": "off", 27 | "no-param-reassign": "off", 28 | 29 | "max-len": ["error", { "code": 80, "ignoreComments": true }], 30 | 31 | // 不要求对 import 进行排序 32 | "import/order": "off", 33 | 34 | "import/no-mutable-exports": "off", 35 | 36 | "import/prefer-default-export": "off", 37 | 38 | // 不要求 import 的模块一定要存在, 因为有自定义的 resolve 和 alias 39 | "import/no-unresolved": "off", 40 | 41 | // 使用双引号 42 | "quotes": ["error", "double"], 43 | 44 | // 禁止多余的逗号 45 | "comma-dangle": ["error", "never"], 46 | 47 | // 强制要求类成员之间要保留空行, 但允许单行类成员声明之间没有空行 48 | "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], 49 | 50 | // 强制要求多行代码块只有要保留空行 51 | "padding-line-between-statements": [ 52 | "error", 53 | { "blankLine": "always", "prev": "class", "next": "*" }, 54 | { "blankLine": "always", "prev": "function", "next": "*" }, 55 | { "blankLine": "always", "prev": "iife", "next": "*" }, 56 | { "blankLine": "always", "prev": "multiline-block-like", "next": "*" }, 57 | { "blankLine": "always", "prev": "multiline-expression", "next": "*" } 58 | ], 59 | 60 | // 不允许使用 @deprecated 的变量或函数 61 | "import/no-deprecated": "error", 62 | 63 | // jsdoc @param 的名字和顺序必须和定义的一致 64 | "jsdoc/check-param-names": "error", 65 | 66 | // jsdoc 中 @ 开头的 tag 名称必须符合规范 67 | "jsdoc/check-tag-names": "error", 68 | 69 | // 不允许出现未使用的 eslint-disable 70 | "eslint-comments/no-unused-disable": "error", 71 | 72 | // 允许空箭头函数 73 | "@typescript-eslint/no-empty-function": [ 74 | "error", 75 | { "allow": ["arrowFunctions"] } 76 | ], 77 | 78 | // 允许 import 文件时缺失后缀 79 | "import/extensions": "off" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tsw@tencent.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Checklist:** 2 | 3 | - [ ] test cases has added or updated 4 | - [ ] documentation has added or updated 5 | - [ ] commit message follows the [convention commit guidelines](https://conventionalcommits.org/) 6 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test-code.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - release 8 | - 2.0 9 | 10 | jobs: 11 | lint-code: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Pull Code 15 | uses: actions/checkout@v1 16 | - name: Set up Node.js 17 | uses: actions/setup-node@master 18 | with: 19 | node-version: '12.x' 20 | - name: Install Dependencies 21 | run: yarn 22 | - name: Commit Linter 23 | uses: wagoid/commitlint-github-action@v1.2.2 24 | with: 25 | configFile: '.commitlintrc.yml' 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | - name: Run ESLinter 29 | run: yarn lint 30 | - name: Run Tests 31 | run: yarn test 32 | - name: codecov 33 | uses: codecov/codecov-action@v1 34 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-npm-alpha.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | 7 | jobs: 8 | publish-to-npm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Pull code 12 | uses: actions/checkout@v1 13 | - name: Set up node.js 14 | uses: actions/setup-node@master 15 | with: 16 | node-version: '16.x' 17 | - name: Install dependencies 18 | run: yarn 19 | - name: build 20 | run: yarn build 21 | - name: Release 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | run: npx 'semantic-release@^18.0.1' 26 | test: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Pull code 30 | uses: actions/checkout@v1 31 | - name: Install Dependencies 32 | run: yarn 33 | - name: Run Tests 34 | run: yarn test 35 | - name: codecov 36 | uses: codecov/codecov-action@v1 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | .DS_Store 5 | dist 6 | .nyc_output 7 | coverage 8 | *.log 9 | benchmark/metricsResult 10 | *.pem 11 | .env -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branch": ["master"], 3 | "tagFormat": "v${version}", 4 | "dryRun": false, 5 | "debug": true, 6 | "plugins": [ 7 | "@semantic-release/commit-analyzer", 8 | "@semantic-release/release-notes-generator", 9 | "@semantic-release/npm", 10 | "@semantic-release/github" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Tencent is pleased to support the open source community by making Tencent Server Web available. 2 | Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 3 | If you have downloaded a copy of the Tencent Server Web binary from Tencent, please note that the Tencent Server Web binary is licensed under the MIT License. 4 | If you have downloaded a copy of the Tencent Server Web source code from Tencent, please note that Tencent Server Web source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of Tencent Server Web into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within Tencent Server Web. 5 | A copy of the MIT License is included in this file. 6 | 7 | Other dependencies and licenses: 8 | 9 | Open Source Software Licensed Under the BSD 3-Clause License: 10 | ---------------------------------------------------------------------------------------- 11 | 1. qs 6.5.0 12 | Copyright (c) 2014 Nathan LaFreniere and other contributors. 13 | All rights reserved. 14 | 15 | The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2018 THL A29 Limited. 16 | 17 | 2. highlight.js 9.12.0 18 | Copyright (c) 2006, Ivan Sagalaev 19 | All rights reserved. 20 | 21 | 22 | Terms of the BSD 3-Clause License: 23 | -------------------------------------------------------------------- 24 | 25 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 26 |  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 27 |  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 28 |  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | Open Source Software Licensed Under the MIT License: 33 | ---------------------------------------------------------------------------------------- 34 | 1. node-archiver 0.16.0 35 | Copyright (c) 2012-2014 Chris Talkington, contributors. 36 | 37 | 2. cookie 0.3.1 38 | Copyright (c) 2012-2014 Roman Shtylman 39 | Copyright (c) 2015 Douglas Christopher Wilson 40 | 41 | 3. zepto 1.2.0 42 | Copyright (c) 2010-2016 Thomas Fuchs 43 | http://zeptojs.com/ 44 | 45 | 4. memcached 2.1.0 46 | Copyright (c) 2010 Arnout Kazemier,3rd-Eden 47 | 48 | 5. seajs 1.3.0 49 | Copyright (c) 2012 Frank Wang, http://seajs.org/ 50 | 51 | 6. Semantic-UI 2.1.5 52 | Copyright 2014 Contributors 53 | 54 | 7. ws 2.2.1 55 | Copyright(c) 2011 Einar Otto Stangvik 56 | 57 | 8. node-zip-stream 0.6.0 58 | Copyright (c) 2014 Chris Talkington, contributors. 59 | 60 | 61 | Terms of the MIT License: 62 | --------------------------------------------------- 63 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Tencent Server Web 2.0](https://tswjs.org) 2 | 3 | 4 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Tencent/TSW/blob/master/LICENSE) [![Build Status](https://github.com/tencent/tsw/workflows/build/badge.svg)](https://github.com/Tencent/TSW/actions?query=workflow%3Abuild) [![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest) [![codecov](https://codecov.io/gh/tencent/tsw/branch/master/graph/badge.svg)](https://codecov.io/gh/tencent/tsw) 5 | 6 |

What is it

7 | 8 | Tencent Server Web(TSW) 是一套面向 WEB 前端开发者,以提升问题定位效率为初衷,提供 **染色抓包** 和 **全息日志** 的 Node.js 基础设施。TSW 关注业务的运维监控能力,适用于 http、https 协议的业务场景,可无缝与现有应用(Koa、Express)进行整合。 9 | 10 | TSW 2.0 在 1.0 的基础上抽丝剥茧,辅以现代化的设计模式,去除了 1.0 中的大量糟粕,同时对容器化、云原生更加友好。做到了无侵入、低成本接入。 11 | 12 |

Highlights

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

🚀

0 侵入

📒

全息日志

🛠

请求抓包

通过 Hack NodeJS 底层代码实现功能。对原有业务代码 0 侵入。按照请求聚类的显微镜级别的全息日志,给开发者完美的现场还原。可抓取 Server 端向外部发送的所有请求的完整包体内容,与后台沟通再无障碍。
26 | 27 |

特别提醒

28 | 29 | ### TSW 1.0 迁移到 2.0 需要注意的地方 30 | 31 | - TSW 1.0 服务包含进程管理,日志管理等多项内置模块,在进行往 2.0 迁移的时候,请选择其他组件完成替代,如引入 PM2 管理 Node 进程,使用 winston 进行日志管理 32 | - 另外 2.0 没有包含日志清理工具,建议选择 winston-rotating-file 类似工具来完成清理,避免日志存放过多,导致磁盘空间不足 33 | 34 | 35 |

Quick Start

36 | 37 | ### 1. 安装 38 | ```bash 39 | npm install --save @tswjs/tsw 40 | // yarn add @tswjs/tsw 41 | ``` 42 | ### 2. 添加配置文件 43 | 配置文件是 TSW 启动时加载进运行时的配置文件,主要声明需要使用的 [插件](#plugins) 列表。**默认会加载项目根目录下的 `tswconfig.js` 文件,也可以通过启动参数 `-c` 或者 `--config` 来手动指定配置文件路径。** 44 | 45 | **注意事项**: 2.0 中没有集成开放平台相关逻辑,而是封装成了一个插件让用户按需使用,详情见[插件](#plugins)章节。 46 | 47 | **配置文件示例:** 48 | ```js 49 | module.exports = { 50 | plugins: [ 51 | new MyPlugin({}) 52 | ] 53 | } 54 | ``` 55 | **参数列表**: 56 | | Name | Type | default | Optional | Description | 57 | | :-: | :-: | :-: | :-: | :-: | 58 | | plugins | Array<[Plugin](#plugins)>| - | yes | [插件](#plugins)列表 | 59 | | cleanLog | boolean | `false` | yes | 是否关闭默认打印 | 60 | | logLevel | `DEBUG/INFO/WARN/ERROR` | `DEBUG` | yes | 设置 log level | 61 | | winstonTransports | Array<[TransportStream](https://github.com/winstonjs/winston-transport/blob/master/index.d.ts)> | - | yes | [Winston](#winston-是什么)日志通道 | 62 | ### 3. 启动 63 | ```bash 64 | npx @tswjs/tsw ./index.js 65 | ``` 66 | 67 | **注意事项**:原先 `node --inspect ./index.js` 中的 CLI 参数如 `--inspect` 需要转化为环境变量 `NODE_OPTIONS` 来执行,如 `NODE_OPTIONS="--inspect" npx @tswjs/tsw ./index.js`。 68 | 69 | **使用 ts**: 在保证项目有 [ts-node](https://www.npmjs.com/package/ts-node) 依赖包的情况下,按照如下方式执行即可直接加载 ts 文件。 70 | ```bash 71 | NODE_OPTIONS="--require=ts-node/register" npx @tswjs/tsw ./index.ts 72 | ``` 73 | ### CLI (Command Line Interface) 74 | 75 | 使用 `npx @tswjs/tsw --help` 来获取 CLI 选项。 76 | 77 | ### Examples 78 | 79 | 我们提供了一些示例项目以让大家尽快了解该项目。 80 | 81 | 1. `cd ~` 82 | 2. `git clone https://github.com/Tencent/TSW.git` 83 | 3. `cd TSW` 84 | 85 | #### Koa 86 | 87 | 1. `cd examples/koa` 88 | 1. `yarn` 89 | 1. `yarn serve` 或者 `npm run serve` 90 | 1. `curl -v localhost:4443/path/to/foo -X POST -d "hello, server"` 91 | 92 |

Plugins

93 | 94 | ### 插件是什么? 95 | 96 | TSW 核心的实现方式是 Hack NodeJS 自身的 `http.request` 以及 `http.createServer`, 以此来实现抓包机制。在服务器处理请求的前后,在服务器向其他服务器发包的前后,等等,都会有相应的事件抛出,以供用户来进行自定义处理。**为了让用户更加方便地复用、传播这样一组组自定义处理,我们将他们抽象出来,形成了插件机制。** 97 | 98 | ### 一个最简单的插件 99 | 100 | ```js 101 | export.modules = class MyPlugin() { 102 | constructor() { 103 | this.name = "MyPlugin" 104 | } 105 | 106 | async init(eventBus, config) { 107 | eventBus.on("RESPONSE_CLOSE", (payload) => { 108 | console.log(payload); 109 | }) 110 | } 111 | } 112 | ``` 113 | 114 | `init` 方法是必须的,这个方法在插件加载开始时会被调用,可以是同步也可以是异步。 115 | 116 | #### `eventBus` 117 | 118 | `eventBus` 是通过 `new EventEmitter()` 得到的。TSW 核心会在各个关键时机触发上面的事件。 119 | 120 | | key | 含义(触发时机) | payload | 121 | | -- | -- | -- | 122 | | `DNS_LOOKUP_SUCCESS` | 在每次 DNS 查询成功之后触发 | `string \| dns.LookupAddress[]` | 123 | | `DNS_LOOKUP_ERROR` | 在每次 DNS 查询失败之后触发 | `NodeJS.ErrorException` | 124 | | `RESPONSE_START` | 在每次服务器开始返回响应(执行 `writeHead`)时触发 | `ResponseEventPayload` | 125 | | `RESPONSE_FINISH` | 在响应结束时(`res.on("finish")`)触发 | `ResponseEventPayload` | 126 | | `RESPONSE_CLOSE` | 在底层链接关闭时 (`res.on("close")`)触发 | `ResponseEventPayload` | 127 | | `REQUEST_START` | 在每次服务器接受到新的请求时触发 | `RequestEventPayload` | 128 | 129 | 130 |

Open Platform

131 | 132 | 在默认的情况下,TSW 只是会把所有的日志和抓包内容抓取到并且送到事件总线上,以供 [插件](#插件是什么?) 消费。所以将日志和抓包内容落地查看一般需要用户自己编写插件以及提供存储,使用成本过于高昂。 133 | 因此,TSW 官方提供了公共的服务平台 [https://tswjs.org](https://tswjs.org),让用户低成本、更快、更方便地使用 TSW 的特性,详情见 [开放平台使用指引](./docs/use-open-platform.md)。 134 | 135 |

Cluster

136 | 137 | TSW 2.0 是面对容器化和云原生设计的,所以没有内置 Cluster 相关功能,推荐直接使用容器的健康检查来完成服务的无损重启和故障重启机制。对于没有使用容器化方案的场景来说,我们推荐使用 [pm2](https://github.com/Unitech/pm2) 类似工具来实现多进程模式。 138 | 139 | ### pm2 140 | 141 | #### 使用 Ecosystem File 142 | 143 | ```js 144 | // ecosystem.config.json 145 | 146 | { 147 | "apps": [ 148 | { 149 | "name": "app-name", 150 | "script": "built/index.js", 151 | "interpreter": "node", 152 | "interpreter_args": "./node_modules/@tswjs/tsw/dist/cli.js", 153 | // other options 154 | } 155 | ] 156 | } 157 | ``` 158 | 159 | ```js 160 | // package.json 161 | 162 | { 163 | ... 164 | "scripts": { 165 | "start": "pm2 start ecosystem.config.json" 166 | }, 167 | ... 168 | } 169 | ``` 170 | 171 |

Winston

172 | 173 | ### winston 是什么? 174 | 175 | `winston` 是一个通用且轻量的日志包。`winston` 支持多个日志通道,并且可以分别定义日志优先级。除了内置的三个日志传输通道[`Console`、 `File`、`HTTP`](https://github.com/winstonjs/winston#common-transport-options),在 Winston 项目外部还会维护一些[传输模块](https://github.com/winstonjs)。查看 `winston` [官方文档](https://github.com/winstonjs/winston)。 176 | 177 | TSW 2.0 支持使用 `winston` 传输通道记录日志信息,用户在配置文件中可以添加 `winston.transports` 实例,日志会落到对应配置中。 178 | 179 | ### 一个简单的示例 180 | 181 | 使用 `winston` 记录 `error` 级别 以及 `debug` 级别以下的日志信息到对应文件中,当前 `config` 文件配置如下: 182 | 183 | ```js 184 | module.exports = { 185 | winstonTransports: [ 186 | new winston.transports.File({ filename: 'error.log', level: 'error'}), 187 | new winston.transports.File({ filename: 'debug.log', level: 'debug'}) 188 | ] 189 | } 190 | ``` 191 | 192 | **日志记录** 193 | 194 | ![log](./static/images/winston-log.png) 195 | 196 |

Users

197 | 198 | [![tsw](./static/images/user/01.png)](https://qzone.qq.com/ "QQ空间")    ![tsw](./static/images/user/02.png)    [![tsw](./static/images/user/03.png)](https://www.weiyun.com/ "腾讯微云")    199 |   200 |   201 | [![tsw](./static/images/user/04.png)](https://fm.qq.com/ "企鹅FM")    [![tsw](./static/images/user/05.png)](https://www.weishi.com/ "微视")    ![tsw](./static/images/user/06.png)     202 |   203 |   204 | [![tsw](./static/images/user/07.png)](http://vip.qq.com/ "QQ会员")    [![tsw](./static/images/user/08.png)](http://egame.qq.com/ "企鹅电竞")    [![tsw](./static/images/user/09.png)](http://ac.qq.com/ "QQ动漫")     205 |   206 |   207 | [![tsw](./static/images/user/10.png)](https://buluo.qq.com/ "QQ兴趣部落")    [![tsw](./static/images/user/11.png)](http://now.qq.com/ "NOW直播")    ![tsw](./static/images/user/12.png)     208 |   209 |   210 | ![tsw](./static/images/user/13.png)    [![tsw](./static/images/user/14.png)](http://yundong.qq.com/ "QQ运动")    ![tsw](./static/images/user/15.png)     211 |   212 |   213 | [![tsw](./static/images/user/16.png)](https://mp.qq.com/ "QQ公众平台")    [![tsw](./static/images/user/17.png)](https://office.qq.com/ "TIM")    [![tsw](./static/images/user/18.png)](http://tianqi.qq.com/index.htm "腾讯天气")     214 |   215 |   216 | [![tsw](./static/images/user/19.png)](https://ke.qq.com/ "腾讯课堂")    [![tsw](./static/images/user/20.png)](https://cloud.tencent.com/ "腾讯云")    [![tsw](./static/images/user/21.png)](https://y.qq.com/ "QQ音乐")     217 |   218 |   219 | [![tsw](./static/images/user/22.png)](https://kg.tencent.com/ "全民K歌")    ![tsw](./static/images/user/23.png)    [![tsw](./static/images/user/24.png)](http://e.qq.com/ads/ "广点通")     220 |   221 |   222 | [![tsw](./static/images/user/25.png)](http://open.qq.com/ "腾讯开放平台")    [![tsw](./static/images/user/26.png)](http://kk.qq.com/ "企鹅看看")    [![tsw](./static/images/user/27.png)](http://sports.qq.com/ "腾讯体育") 223 | 224 |

License

225 | 226 | Tencent Server Web 的开源协议为 MIT, 详情参见 [LICENSE](https://github.com/Tencent/TSW/blob/master/LICENSE) 。 227 | -------------------------------------------------------------------------------- /benchmark/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @./run start/start metricsResult/resultByOrigin.txt 3 | @./run start/startByTsw metricsResult/resultByTsw.txt 4 | @./run start/startByTswNoReport metricsResult/resultByTswNoReport.txt 5 | @echo 6 | 7 | .PHONY: all -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | ## How to run benchmark 2 | 3 | ### 1. Install wrk 4 | ``` 5 | // macOS 6 | brew install wrk 7 | ``` 8 | ### 2. Install Npm 9 | ``` 10 | cd benchmark && npm i 11 | ``` 12 | ### 3. Check if dist exists 13 | ``` 14 | // build if necessary 15 | npm run build 16 | ``` 17 | ### 4. Run the job 18 | ``` 19 | npm run benchmark 20 | ``` -------------------------------------------------------------------------------- /benchmark/originServer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | const http = require("http"); 4 | 5 | const server = http.createServer((req, res) => { 6 | res.end("hello world"); 7 | }); 8 | 9 | server.listen(3000); 10 | console.log("origin node server is listening on 3000"); 11 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "makefile", 6 | "author": "", 7 | "license": "ISC", 8 | "devDependencies": { 9 | "xl_close_port": "^1.0.1" 10 | }, 11 | "dependencies": { 12 | "@tswjs/open-platform-plugin": "^2.0.9", 13 | "ip": "^1.1.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /benchmark/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | node $1 & 4 | pid=$! 5 | 6 | if [ ! -d metricsResult ];then 7 | mkdir metricsResult 8 | fi 9 | 10 | sleep 2 11 | 12 | wrk 'http://localhost:3000/' \ 13 | -d 3 \ 14 | -c 50 \ 15 | -t 8 \ 16 | > $2 17 | 18 | npx xl_close_port -p 3000 19 | kill $pid -------------------------------------------------------------------------------- /benchmark/start/start.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { spawn } = require("child_process"); 3 | 4 | spawn( 5 | "sh", 6 | [ 7 | "-c", 8 | "npx xl_close_port -p 3000 && node ./originServer.js" 9 | ], { 10 | stdio: ["pipe", "inherit", "inherit"] 11 | } 12 | ); 13 | -------------------------------------------------------------------------------- /benchmark/start/startByTsw.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const { spawn } = require("child_process"); 4 | 5 | spawn( 6 | "sh", 7 | [ 8 | "-c", 9 | "npx xl_close_port -p 3000 && node ../dist/cli.js ./originServer.js -c ./start/tswconfig.js" 10 | ], { 11 | stdio: ["pipe", "inherit", "inherit"] 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /benchmark/start/startByTswNoReport.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const { spawn } = require("child_process"); 4 | 5 | spawn( 6 | "sh", 7 | [ 8 | "-c", 9 | "npx xl_close_port -p 3000 && node ../dist/cli.js ./originServer.js -c ./start/tswconfigNoReport.js" 10 | ], { 11 | stdio: ["pipe", "inherit", "inherit"] 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /benchmark/start/tswconfig.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const winston = require("winston"); 4 | const OpenPlatformPlugin = require("@tswjs/open-platform-plugin"); 5 | 6 | module.exports = { 7 | plugins: [ 8 | new OpenPlatformPlugin({ 9 | reportStrategy: "always", 10 | // 只支持同步写法 11 | getUid: (request) => "xxx", 12 | getProxyInfo: () => ({ 13 | port: 80, 14 | name: "benchmark", 15 | group: "TSW", 16 | groupName: "TSW团队", 17 | desc: "benchmark测试环境", 18 | order: 30, 19 | owner: "demoUser", 20 | alphaList: ["demoUser"] 21 | }) 22 | }) 23 | ], 24 | winstonTransports: [ 25 | new winston.transports.File({ filename: "error.log", level: "error" }), 26 | new winston.transports.File({ filename: "debug.log", level: "debug" }) 27 | ] 28 | // logLevel: "ERROR", 29 | // cleanLog: true 30 | }; 31 | -------------------------------------------------------------------------------- /benchmark/start/tswconfigNoReport.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const winston = require("winston"); 4 | const OpenPlatformPlugin = require("@tswjs/open-platform-plugin"); 5 | 6 | module.exports = { 7 | plugins: [ 8 | new OpenPlatformPlugin({ 9 | reportStrategy: "never" 10 | }) 11 | ], 12 | winstonTransports: [ 13 | new winston.transports.File({ filename: "error.log", level: "error" }), 14 | new winston.transports.File({ filename: "debug.log", level: "debug" }) 15 | ] 16 | }; 17 | -------------------------------------------------------------------------------- /docs/use-open-platform.md: -------------------------------------------------------------------------------- 1 | # 使用 TSW 开放平台 2 | 3 | 使用 TSW 开放平台的具体步骤如下: 4 | 5 | 1. 登录 https://tswjs.org 并在其上新建一个应用 6 | 7 | ![create-app](../static/images/create-app.png) 8 | 9 | 1. 打开应用,获取 `appid` 和 `appkey` 10 | 11 | ![appid-appkey](../static/images/appid-appkey.png) 12 | 13 | 1. 在项目根目录下新增配置文件 `tswconfig.js`,并参照 [开放平台插件](https://github.com/tswjs/open-platform/blob/master/packages/open-platform-plugin/README.md) 指引配置完成。 14 | 15 | 1. 向之前启动的 Koa 或者原生 http server 发送请求,并且在开放平台上查看对应的日志和抓包。查看地址为下方地址拼接而成 `https://domain/log/view/YOUR_UID` 16 | 17 | ![log-view](../static/images/log-view.png) 18 | 19 | **日志记录** 20 | 21 | ![log](../static/images/log.png) 22 | 23 | **在线查看抓包内容** 24 | 25 | ![capture](../static/images/capture.png) 26 | -------------------------------------------------------------------------------- /examples/https/README.md: -------------------------------------------------------------------------------- 1 | ## How to run demo 2 | 3 | ### 1. Generate a self-signed certificate (key.pem and cert.pem) 4 | ```bash 5 | openssl genrsa -out key.pem 6 | openssl req -new -key key.pem -out csr.pem 7 | openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem 8 | rm csr.pem 9 | ``` 10 | 11 | ### 2. Install Npm 12 | ```bash 13 | yarn 14 | // npm i 15 | ``` 16 | 17 | ### 3. Start the server 18 | ``` 19 | npm run serve 20 | ``` -------------------------------------------------------------------------------- /examples/https/index.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const options = { 6 | key: fs.readFileSync(path.resolve(__dirname, 'key.pem')), 7 | cert: fs.readFileSync(path.resolve(__dirname, 'cert.pem')) 8 | }; 9 | 10 | https.createServer(options, function (req, res) { 11 | console.log("hello world"); 12 | 13 | res.writeHead(200); 14 | res.end("hello world\n"); 15 | }).listen(8000); 16 | 17 | console.log("origin node server is listening on 8000"); 18 | 19 | -------------------------------------------------------------------------------- /examples/https/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-example", 3 | "scripts": { 4 | "serve": "tsw ./index.js", 5 | "serve:inspect": "NODE_OPTIONS='--inspect=localhost:4442' tsw ./index.js" 6 | }, 7 | "version": "1.0.0", 8 | "main": "index.js", 9 | "license": "MIT", 10 | "dependencies": { 11 | "@tswjs/open-platform-plugin": "^1.3.1", 12 | "@tswjs/tsw": "^2.6.0", 13 | "winston": "^3.2.1", 14 | "winston-transport": "^4.3.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/https/tswconfig.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const OpenPlatformPlugin = require("@tswjs/open-platform-plugin"); 3 | 4 | module.exports = { 5 | plugins: [ 6 | new OpenPlatformPlugin({ 7 | reportStrategy: "proxied", 8 | // 只支持同步写法 9 | getUid: (request) => "xxx", 10 | getProxyInfo: () => { 11 | return { 12 | "port": 80, 13 | "name": "2.0demo", 14 | "group": "TSW", 15 | "groupName": "TSW团队", 16 | "desc": "2.0demo测试环境", 17 | "order": 30, 18 | "owner": "demoUser", 19 | "alphaList": ["xxx"] 20 | }; 21 | } 22 | }) 23 | ], 24 | winstonTransports: [ 25 | new winston.transports.File({ filename: 'error.log', level: 'error'}), 26 | new winston.transports.File({ filename: 'debug.log', level: 'debug'}) 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /examples/https/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@dabh/diagnostics@^2.0.2": 6 | version "2.0.2" 7 | resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" 8 | integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== 9 | dependencies: 10 | colorspace "1.1.x" 11 | enabled "2.0.x" 12 | kuler "^2.0.0" 13 | 14 | "@tootallnate/once@1": 15 | version "1.1.2" 16 | resolved "http://mirrors.tencent.com/npm/@tootallnate%2fonce/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" 17 | integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== 18 | 19 | "@tswjs/open-platform-api@^1.2.2": 20 | version "1.2.2" 21 | resolved "https://mirrors.tencent.com/npm/@tswjs%2fopen-platform-api/-/open-platform-api-1.2.2.tgz#8b0a874e2b448ca9a517cfc22916ca693eb535d1" 22 | integrity sha512-PkicdbN0PRRIX5VmsARk3PQp51+00M7U9X3XkupwwKbuxckxZKYTmwFxTs7Wl1NkS8q5HLSqB7OyLr8mZ8YySw== 23 | dependencies: 24 | http-proxy-agent "^4.0.1" 25 | https-proxy-agent "^5.0.0" 26 | node-fetch "^2.6.0" 27 | proxy-from-env "^1.1.0" 28 | 29 | "@tswjs/open-platform-plugin@^1.3.1": 30 | version "1.3.1" 31 | resolved "https://mirrors.tencent.com/npm/@tswjs%2fopen-platform-plugin/-/open-platform-plugin-1.3.1.tgz#e620e0f5bd4b3cb93bdea52e0893222fd5346bc9" 32 | integrity sha512-w0CW2tjcSL8b+FGn4v/Dan2jSOo0pM4O+4UJsU16F/MajVeL3jjbxlyCko25b8ZMFAZRaAYSFoiKpMFnSJd0JA== 33 | dependencies: 34 | "@tswjs/open-platform-api" "^1.2.2" 35 | ip "^1.1.5" 36 | 37 | "@tswjs/tsw@^2.6.0": 38 | version "2.6.0" 39 | resolved "https://mirrors.tencent.com/npm/@tswjs%2ftsw/-/tsw-2.6.0.tgz#413705d38db7115d31d44c1a660227432c23e87c" 40 | integrity sha512-A6yVdf5nmfo5KH4a30rYJL+3l1rgs87YVmtk83oqULIUsarZujjyDchh3J/kf2GOTQM6EkOm6KgyBS55NidHNA== 41 | dependencies: 42 | chalk "^3.0.0" 43 | ip "^1.1.5" 44 | lodash "^4.17.15" 45 | moment "^2.24.0" 46 | patch-package "^6.2.2" 47 | postinstall-postinstall "^2.1.0" 48 | winston "^3.2.1" 49 | winston-transport "^4.3.0" 50 | yargs "^15.3.1" 51 | 52 | "@yarnpkg/lockfile@^1.1.0": 53 | version "1.1.0" 54 | resolved "http://mirrors.tencent.com/npm/@yarnpkg%2flockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" 55 | integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== 56 | 57 | agent-base@6: 58 | version "6.0.2" 59 | resolved "http://mirrors.tencent.com/npm/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 60 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 61 | dependencies: 62 | debug "4" 63 | 64 | ansi-regex@^5.0.0: 65 | version "5.0.1" 66 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 67 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 68 | 69 | ansi-styles@^3.2.1: 70 | version "3.2.1" 71 | resolved "https://mirrors.tencent.com/npm/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 72 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 73 | dependencies: 74 | color-convert "^1.9.0" 75 | 76 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 77 | version "4.3.0" 78 | resolved "https://mirrors.tencent.com/npm/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 79 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 80 | dependencies: 81 | color-convert "^2.0.1" 82 | 83 | async@^3.1.0: 84 | version "3.2.3" 85 | resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" 86 | integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== 87 | 88 | balanced-match@^1.0.0: 89 | version "1.0.2" 90 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 91 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 92 | 93 | brace-expansion@^1.1.7: 94 | version "1.1.11" 95 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 96 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 97 | dependencies: 98 | balanced-match "^1.0.0" 99 | concat-map "0.0.1" 100 | 101 | braces@^3.0.1: 102 | version "3.0.3" 103 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 104 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 105 | dependencies: 106 | fill-range "^7.1.1" 107 | 108 | camelcase@^5.0.0: 109 | version "5.3.1" 110 | resolved "https://mirrors.tencent.com/npm/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 111 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 112 | 113 | chalk@^2.4.2: 114 | version "2.4.2" 115 | resolved "http://mirrors.tencent.com/npm/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 116 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 117 | dependencies: 118 | ansi-styles "^3.2.1" 119 | escape-string-regexp "^1.0.5" 120 | supports-color "^5.3.0" 121 | 122 | chalk@^3.0.0: 123 | version "3.0.0" 124 | resolved "http://mirrors.tencent.com/npm/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" 125 | integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== 126 | dependencies: 127 | ansi-styles "^4.1.0" 128 | supports-color "^7.1.0" 129 | 130 | ci-info@^2.0.0: 131 | version "2.0.0" 132 | resolved "http://mirrors.tencent.com/npm/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" 133 | integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== 134 | 135 | cliui@^6.0.0: 136 | version "6.0.0" 137 | resolved "http://mirrors.tencent.com/npm/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" 138 | integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== 139 | dependencies: 140 | string-width "^4.2.0" 141 | strip-ansi "^6.0.0" 142 | wrap-ansi "^6.2.0" 143 | 144 | color-convert@^1.9.0, color-convert@^1.9.1: 145 | version "1.9.3" 146 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 147 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 148 | dependencies: 149 | color-name "1.1.3" 150 | 151 | color-convert@^2.0.1: 152 | version "2.0.1" 153 | resolved "http://mirrors.tencent.com/npm/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 154 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 155 | dependencies: 156 | color-name "~1.1.4" 157 | 158 | color-name@1.1.3: 159 | version "1.1.3" 160 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 161 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 162 | 163 | color-name@^1.0.0, color-name@~1.1.4: 164 | version "1.1.4" 165 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 166 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 167 | 168 | color-string@^1.5.2: 169 | version "1.5.5" 170 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" 171 | integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== 172 | dependencies: 173 | color-name "^1.0.0" 174 | simple-swizzle "^0.2.2" 175 | 176 | color@3.0.x: 177 | version "3.0.0" 178 | resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" 179 | integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== 180 | dependencies: 181 | color-convert "^1.9.1" 182 | color-string "^1.5.2" 183 | 184 | colors@^1.2.1: 185 | version "1.4.0" 186 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" 187 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== 188 | 189 | colorspace@1.1.x: 190 | version "1.1.2" 191 | resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" 192 | integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== 193 | dependencies: 194 | color "3.0.x" 195 | text-hex "1.0.x" 196 | 197 | concat-map@0.0.1: 198 | version "0.0.1" 199 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 200 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 201 | 202 | core-util-is@~1.0.0: 203 | version "1.0.2" 204 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 205 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 206 | 207 | cross-spawn@^6.0.5: 208 | version "6.0.5" 209 | resolved "http://mirrors.tencent.com/npm/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" 210 | integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== 211 | dependencies: 212 | nice-try "^1.0.4" 213 | path-key "^2.0.1" 214 | semver "^5.5.0" 215 | shebang-command "^1.2.0" 216 | which "^1.2.9" 217 | 218 | debug@4: 219 | version "4.3.1" 220 | resolved "https://mirrors.tencent.com/npm/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 221 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== 222 | dependencies: 223 | ms "2.1.2" 224 | 225 | decamelize@^1.2.0: 226 | version "1.2.0" 227 | resolved "http://mirrors.tencent.com/npm/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 228 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 229 | 230 | emoji-regex@^8.0.0: 231 | version "8.0.0" 232 | resolved "http://mirrors.tencent.com/npm/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 233 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 234 | 235 | enabled@2.0.x: 236 | version "2.0.0" 237 | resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" 238 | integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== 239 | 240 | escape-string-regexp@^1.0.5: 241 | version "1.0.5" 242 | resolved "http://mirrors.tencent.com/npm/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 243 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 244 | 245 | fast-safe-stringify@^2.0.4: 246 | version "2.0.7" 247 | resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" 248 | integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== 249 | 250 | fecha@^4.2.0: 251 | version "4.2.0" 252 | resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" 253 | integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== 254 | 255 | fill-range@^7.1.1: 256 | version "7.1.1" 257 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 258 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 259 | dependencies: 260 | to-regex-range "^5.0.1" 261 | 262 | find-up@^4.1.0: 263 | version "4.1.0" 264 | resolved "http://mirrors.tencent.com/npm/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 265 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 266 | dependencies: 267 | locate-path "^5.0.0" 268 | path-exists "^4.0.0" 269 | 270 | find-yarn-workspace-root@^2.0.0: 271 | version "2.0.0" 272 | resolved "http://mirrors.tencent.com/npm/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" 273 | integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== 274 | dependencies: 275 | micromatch "^4.0.2" 276 | 277 | fn.name@1.x.x: 278 | version "1.1.0" 279 | resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" 280 | integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== 281 | 282 | fs-extra@^7.0.1: 283 | version "7.0.1" 284 | resolved "http://mirrors.tencent.com/npm/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" 285 | integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== 286 | dependencies: 287 | graceful-fs "^4.1.2" 288 | jsonfile "^4.0.0" 289 | universalify "^0.1.0" 290 | 291 | fs.realpath@^1.0.0: 292 | version "1.0.0" 293 | resolved "http://mirrors.tencent.com/npm/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 294 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 295 | 296 | get-caller-file@^2.0.1: 297 | version "2.0.5" 298 | resolved "http://mirrors.tencent.com/npm/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 299 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 300 | 301 | glob@^7.1.3: 302 | version "7.1.7" 303 | resolved "http://mirrors.tencent.com/npm/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" 304 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== 305 | dependencies: 306 | fs.realpath "^1.0.0" 307 | inflight "^1.0.4" 308 | inherits "2" 309 | minimatch "^3.0.4" 310 | once "^1.3.0" 311 | path-is-absolute "^1.0.0" 312 | 313 | graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: 314 | version "4.2.6" 315 | resolved "http://mirrors.tencent.com/npm/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" 316 | integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== 317 | 318 | has-flag@^3.0.0: 319 | version "3.0.0" 320 | resolved "http://mirrors.tencent.com/npm/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 321 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 322 | 323 | has-flag@^4.0.0: 324 | version "4.0.0" 325 | resolved "http://mirrors.tencent.com/npm/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 326 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 327 | 328 | http-proxy-agent@^4.0.1: 329 | version "4.0.1" 330 | resolved "http://mirrors.tencent.com/npm/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" 331 | integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== 332 | dependencies: 333 | "@tootallnate/once" "1" 334 | agent-base "6" 335 | debug "4" 336 | 337 | https-proxy-agent@^5.0.0: 338 | version "5.0.0" 339 | resolved "http://mirrors.tencent.com/npm/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" 340 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== 341 | dependencies: 342 | agent-base "6" 343 | debug "4" 344 | 345 | inflight@^1.0.4: 346 | version "1.0.6" 347 | resolved "http://mirrors.tencent.com/npm/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 348 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 349 | dependencies: 350 | once "^1.3.0" 351 | wrappy "1" 352 | 353 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 354 | version "2.0.4" 355 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 356 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 357 | 358 | ip@^1.1.5: 359 | version "1.1.9" 360 | resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" 361 | integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== 362 | 363 | is-arrayish@^0.3.1: 364 | version "0.3.2" 365 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" 366 | integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== 367 | 368 | is-ci@^2.0.0: 369 | version "2.0.0" 370 | resolved "http://mirrors.tencent.com/npm/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" 371 | integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== 372 | dependencies: 373 | ci-info "^2.0.0" 374 | 375 | is-docker@^2.0.0: 376 | version "2.2.1" 377 | resolved "http://mirrors.tencent.com/npm/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" 378 | integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== 379 | 380 | is-fullwidth-code-point@^3.0.0: 381 | version "3.0.0" 382 | resolved "http://mirrors.tencent.com/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 383 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 384 | 385 | is-number@^7.0.0: 386 | version "7.0.0" 387 | resolved "https://mirrors.tencent.com/npm/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 388 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 389 | 390 | is-stream@^2.0.0: 391 | version "2.0.0" 392 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" 393 | integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== 394 | 395 | is-wsl@^2.1.1: 396 | version "2.2.0" 397 | resolved "https://mirrors.tencent.com/npm/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" 398 | integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== 399 | dependencies: 400 | is-docker "^2.0.0" 401 | 402 | isarray@~1.0.0: 403 | version "1.0.0" 404 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 405 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 406 | 407 | isexe@^2.0.0: 408 | version "2.0.0" 409 | resolved "http://mirrors.tencent.com/npm/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 410 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 411 | 412 | jsonfile@^4.0.0: 413 | version "4.0.0" 414 | resolved "http://mirrors.tencent.com/npm/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 415 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 416 | optionalDependencies: 417 | graceful-fs "^4.1.6" 418 | 419 | klaw-sync@^6.0.0: 420 | version "6.0.0" 421 | resolved "https://mirrors.tencent.com/npm/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" 422 | integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== 423 | dependencies: 424 | graceful-fs "^4.1.11" 425 | 426 | kuler@^2.0.0: 427 | version "2.0.0" 428 | resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" 429 | integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== 430 | 431 | locate-path@^5.0.0: 432 | version "5.0.0" 433 | resolved "https://mirrors.tencent.com/npm/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 434 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 435 | dependencies: 436 | p-locate "^4.1.0" 437 | 438 | lodash@^4.17.15: 439 | version "4.17.21" 440 | resolved "http://mirrors.tencent.com/npm/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 441 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 442 | 443 | logform@^2.2.0: 444 | version "2.2.0" 445 | resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" 446 | integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== 447 | dependencies: 448 | colors "^1.2.1" 449 | fast-safe-stringify "^2.0.4" 450 | fecha "^4.2.0" 451 | ms "^2.1.1" 452 | triple-beam "^1.3.0" 453 | 454 | micromatch@^4.0.2: 455 | version "4.0.4" 456 | resolved "http://mirrors.tencent.com/npm/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" 457 | integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== 458 | dependencies: 459 | braces "^3.0.1" 460 | picomatch "^2.2.3" 461 | 462 | minimatch@^3.0.4: 463 | version "3.1.2" 464 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 465 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 466 | dependencies: 467 | brace-expansion "^1.1.7" 468 | 469 | minimist@^1.2.0: 470 | version "1.2.6" 471 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 472 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 473 | 474 | moment@^2.24.0: 475 | version "2.29.4" 476 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" 477 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== 478 | 479 | ms@2.1.2, ms@^2.1.1: 480 | version "2.1.2" 481 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 482 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 483 | 484 | nice-try@^1.0.4: 485 | version "1.0.5" 486 | resolved "http://mirrors.tencent.com/npm/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 487 | integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 488 | 489 | node-fetch@^2.6.0: 490 | version "2.6.7" 491 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 492 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 493 | dependencies: 494 | whatwg-url "^5.0.0" 495 | 496 | once@^1.3.0: 497 | version "1.4.0" 498 | resolved "https://mirrors.tencent.com/npm/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 499 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 500 | dependencies: 501 | wrappy "1" 502 | 503 | one-time@^1.0.0: 504 | version "1.0.0" 505 | resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" 506 | integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== 507 | dependencies: 508 | fn.name "1.x.x" 509 | 510 | open@^7.4.2: 511 | version "7.4.2" 512 | resolved "https://mirrors.tencent.com/npm/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" 513 | integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== 514 | dependencies: 515 | is-docker "^2.0.0" 516 | is-wsl "^2.1.1" 517 | 518 | os-tmpdir@~1.0.2: 519 | version "1.0.2" 520 | resolved "http://mirrors.tencent.com/npm/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 521 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 522 | 523 | p-limit@^2.2.0: 524 | version "2.3.0" 525 | resolved "http://mirrors.tencent.com/npm/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 526 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 527 | dependencies: 528 | p-try "^2.0.0" 529 | 530 | p-locate@^4.1.0: 531 | version "4.1.0" 532 | resolved "http://mirrors.tencent.com/npm/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 533 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 534 | dependencies: 535 | p-limit "^2.2.0" 536 | 537 | p-try@^2.0.0: 538 | version "2.2.0" 539 | resolved "http://mirrors.tencent.com/npm/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 540 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 541 | 542 | patch-package@^6.2.2: 543 | version "6.4.7" 544 | resolved "http://mirrors.tencent.com/npm/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" 545 | integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== 546 | dependencies: 547 | "@yarnpkg/lockfile" "^1.1.0" 548 | chalk "^2.4.2" 549 | cross-spawn "^6.0.5" 550 | find-yarn-workspace-root "^2.0.0" 551 | fs-extra "^7.0.1" 552 | is-ci "^2.0.0" 553 | klaw-sync "^6.0.0" 554 | minimist "^1.2.0" 555 | open "^7.4.2" 556 | rimraf "^2.6.3" 557 | semver "^5.6.0" 558 | slash "^2.0.0" 559 | tmp "^0.0.33" 560 | 561 | path-exists@^4.0.0: 562 | version "4.0.0" 563 | resolved "http://mirrors.tencent.com/npm/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 564 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 565 | 566 | path-is-absolute@^1.0.0: 567 | version "1.0.1" 568 | resolved "http://mirrors.tencent.com/npm/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 569 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 570 | 571 | path-key@^2.0.1: 572 | version "2.0.1" 573 | resolved "http://mirrors.tencent.com/npm/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 574 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 575 | 576 | picomatch@^2.2.3: 577 | version "2.3.0" 578 | resolved "http://mirrors.tencent.com/npm/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 579 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 580 | 581 | postinstall-postinstall@^2.1.0: 582 | version "2.1.0" 583 | resolved "https://mirrors.tencent.com/npm/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" 584 | integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== 585 | 586 | process-nextick-args@~2.0.0: 587 | version "2.0.1" 588 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 589 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 590 | 591 | proxy-from-env@^1.1.0: 592 | version "1.1.0" 593 | resolved "http://mirrors.tencent.com/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 594 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 595 | 596 | readable-stream@^2.3.7: 597 | version "2.3.7" 598 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 599 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 600 | dependencies: 601 | core-util-is "~1.0.0" 602 | inherits "~2.0.3" 603 | isarray "~1.0.0" 604 | process-nextick-args "~2.0.0" 605 | safe-buffer "~5.1.1" 606 | string_decoder "~1.1.1" 607 | util-deprecate "~1.0.1" 608 | 609 | readable-stream@^3.4.0: 610 | version "3.6.0" 611 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 612 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 613 | dependencies: 614 | inherits "^2.0.3" 615 | string_decoder "^1.1.1" 616 | util-deprecate "^1.0.1" 617 | 618 | require-directory@^2.1.1: 619 | version "2.1.1" 620 | resolved "http://mirrors.tencent.com/npm/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 621 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 622 | 623 | require-main-filename@^2.0.0: 624 | version "2.0.0" 625 | resolved "http://mirrors.tencent.com/npm/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 626 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 627 | 628 | rimraf@^2.6.3: 629 | version "2.7.1" 630 | resolved "https://mirrors.tencent.com/npm/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 631 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 632 | dependencies: 633 | glob "^7.1.3" 634 | 635 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 636 | version "5.1.2" 637 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 638 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 639 | 640 | safe-buffer@~5.2.0: 641 | version "5.2.1" 642 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 643 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 644 | 645 | semver@^5.5.0, semver@^5.6.0: 646 | version "5.7.2" 647 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" 648 | integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== 649 | 650 | set-blocking@^2.0.0: 651 | version "2.0.0" 652 | resolved "http://mirrors.tencent.com/npm/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 653 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 654 | 655 | shebang-command@^1.2.0: 656 | version "1.2.0" 657 | resolved "http://mirrors.tencent.com/npm/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 658 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 659 | dependencies: 660 | shebang-regex "^1.0.0" 661 | 662 | shebang-regex@^1.0.0: 663 | version "1.0.0" 664 | resolved "http://mirrors.tencent.com/npm/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 665 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 666 | 667 | simple-swizzle@^0.2.2: 668 | version "0.2.2" 669 | resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" 670 | integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= 671 | dependencies: 672 | is-arrayish "^0.3.1" 673 | 674 | slash@^2.0.0: 675 | version "2.0.0" 676 | resolved "http://mirrors.tencent.com/npm/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" 677 | integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== 678 | 679 | stack-trace@0.0.x: 680 | version "0.0.10" 681 | resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 682 | integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= 683 | 684 | string-width@^4.1.0, string-width@^4.2.0: 685 | version "4.2.2" 686 | resolved "https://mirrors.tencent.com/npm/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" 687 | integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== 688 | dependencies: 689 | emoji-regex "^8.0.0" 690 | is-fullwidth-code-point "^3.0.0" 691 | strip-ansi "^6.0.0" 692 | 693 | string_decoder@^1.1.1: 694 | version "1.3.0" 695 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 696 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 697 | dependencies: 698 | safe-buffer "~5.2.0" 699 | 700 | string_decoder@~1.1.1: 701 | version "1.1.1" 702 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 703 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 704 | dependencies: 705 | safe-buffer "~5.1.0" 706 | 707 | strip-ansi@^6.0.0: 708 | version "6.0.0" 709 | resolved "http://mirrors.tencent.com/npm/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 710 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 711 | dependencies: 712 | ansi-regex "^5.0.0" 713 | 714 | supports-color@^5.3.0: 715 | version "5.5.0" 716 | resolved "https://mirrors.tencent.com/npm/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 717 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 718 | dependencies: 719 | has-flag "^3.0.0" 720 | 721 | supports-color@^7.1.0: 722 | version "7.2.0" 723 | resolved "https://mirrors.tencent.com/npm/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 724 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 725 | dependencies: 726 | has-flag "^4.0.0" 727 | 728 | text-hex@1.0.x: 729 | version "1.0.0" 730 | resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" 731 | integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== 732 | 733 | tmp@^0.0.33: 734 | version "0.0.33" 735 | resolved "https://mirrors.tencent.com/npm/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 736 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 737 | dependencies: 738 | os-tmpdir "~1.0.2" 739 | 740 | to-regex-range@^5.0.1: 741 | version "5.0.1" 742 | resolved "http://mirrors.tencent.com/npm/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 743 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 744 | dependencies: 745 | is-number "^7.0.0" 746 | 747 | tr46@~0.0.3: 748 | version "0.0.3" 749 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 750 | integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= 751 | 752 | triple-beam@^1.2.0, triple-beam@^1.3.0: 753 | version "1.3.0" 754 | resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" 755 | integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== 756 | 757 | universalify@^0.1.0: 758 | version "0.1.2" 759 | resolved "http://mirrors.tencent.com/npm/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 760 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 761 | 762 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 763 | version "1.0.2" 764 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 765 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 766 | 767 | webidl-conversions@^3.0.0: 768 | version "3.0.1" 769 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 770 | integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= 771 | 772 | whatwg-url@^5.0.0: 773 | version "5.0.0" 774 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 775 | integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= 776 | dependencies: 777 | tr46 "~0.0.3" 778 | webidl-conversions "^3.0.0" 779 | 780 | which-module@^2.0.0: 781 | version "2.0.0" 782 | resolved "http://mirrors.tencent.com/npm/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 783 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 784 | 785 | which@^1.2.9: 786 | version "1.3.1" 787 | resolved "http://mirrors.tencent.com/npm/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 788 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 789 | dependencies: 790 | isexe "^2.0.0" 791 | 792 | winston-transport@^4.3.0, winston-transport@^4.4.0: 793 | version "4.4.0" 794 | resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" 795 | integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== 796 | dependencies: 797 | readable-stream "^2.3.7" 798 | triple-beam "^1.2.0" 799 | 800 | winston@^3.2.1: 801 | version "3.3.3" 802 | resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" 803 | integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== 804 | dependencies: 805 | "@dabh/diagnostics" "^2.0.2" 806 | async "^3.1.0" 807 | is-stream "^2.0.0" 808 | logform "^2.2.0" 809 | one-time "^1.0.0" 810 | readable-stream "^3.4.0" 811 | stack-trace "0.0.x" 812 | triple-beam "^1.3.0" 813 | winston-transport "^4.4.0" 814 | 815 | wrap-ansi@^6.2.0: 816 | version "6.2.0" 817 | resolved "http://mirrors.tencent.com/npm/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 818 | integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 819 | dependencies: 820 | ansi-styles "^4.0.0" 821 | string-width "^4.1.0" 822 | strip-ansi "^6.0.0" 823 | 824 | wrappy@1: 825 | version "1.0.2" 826 | resolved "https://mirrors.tencent.com/npm/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 827 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 828 | 829 | y18n@^4.0.0: 830 | version "4.0.3" 831 | resolved "http://mirrors.tencent.com/npm/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" 832 | integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== 833 | 834 | yargs-parser@^18.1.2: 835 | version "18.1.3" 836 | resolved "https://mirrors.tencent.com/npm/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" 837 | integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== 838 | dependencies: 839 | camelcase "^5.0.0" 840 | decamelize "^1.2.0" 841 | 842 | yargs@^15.3.1: 843 | version "15.4.1" 844 | resolved "http://mirrors.tencent.com/npm/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" 845 | integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== 846 | dependencies: 847 | cliui "^6.0.0" 848 | decamelize "^1.2.0" 849 | find-up "^4.1.0" 850 | get-caller-file "^2.0.1" 851 | require-directory "^2.1.1" 852 | require-main-filename "^2.0.0" 853 | set-blocking "^2.0.0" 854 | string-width "^4.2.0" 855 | which-module "^2.0.0" 856 | y18n "^4.0.0" 857 | yargs-parser "^18.1.2" 858 | -------------------------------------------------------------------------------- /examples/koa/README.md: -------------------------------------------------------------------------------- 1 | ## How to run demo 2 | 3 | ### 1. Install Npm 4 | ```bash 5 | yarn 6 | // npm i 7 | ``` 8 | 9 | ### 2. Start the server 10 | ``` 11 | npm run serve 12 | ``` -------------------------------------------------------------------------------- /examples/koa/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require("koa"); 2 | const axios = require("axios"); 3 | 4 | const app = new Koa(); 5 | 6 | app.use(async ctx => { 7 | await axios.get( 8 | "http://jsonplaceholder.typicode.com/todos/1" 9 | ).then(res => { 10 | console.log(res.data); 11 | }); 12 | 13 | await axios.post("http://jsonplaceholder.typicode.com/posts", { 14 | body: JSON.stringify({ 15 | title: 'foo', 16 | body: 'bar', 17 | userId: 1 18 | }), 19 | headers: { 20 | "Content-type": "application/json; charset=UTF-8" 21 | } 22 | }).then(res => { 23 | console.log(res.data); 24 | }); 25 | 26 | 27 | ctx.body = "Hello, tsw 2.0"; 28 | ctx.status = 200; 29 | }).listen(4443); 30 | -------------------------------------------------------------------------------- /examples/koa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-example", 3 | "scripts": { 4 | "serve": "tsw ./index.js", 5 | "serve:inspect": "NODE_OPTIONS='--inspect=localhost:4442' tsw ./index.js" 6 | }, 7 | "version": "1.0.0", 8 | "main": "index.js", 9 | "license": "MIT", 10 | "dependencies": { 11 | "@tswjs/open-platform-plugin": "^1.3.3", 12 | "@tswjs/tsw": "^2.6.0", 13 | "axios": "^0.28.0", 14 | "koa": "^2.11.0", 15 | "winston": "^3.2.1", 16 | "winston-transport": "^4.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/koa/tswconfig.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const OpenPlatformPlugin = require("@tswjs/open-platform-plugin"); 3 | 4 | module.exports = { 5 | plugins: [ 6 | new OpenPlatformPlugin({ 7 | reportStrategy: "proxied", 8 | // 只支持同步写法 9 | getUid: (request) => "xxx", 10 | getProxyInfo: () => { 11 | return { 12 | "port": 80, 13 | "name": "2.0demo", 14 | "group": "TSW", 15 | "groupName": "TSW团队", 16 | "desc": "2.0demo测试环境", 17 | "order": 30, 18 | "owner": "demoUser", 19 | "alphaList": ["xxx"] 20 | }; 21 | } 22 | }) 23 | ], 24 | winstonTransports: [ 25 | new winston.transports.File({ filename: 'error.log', level: 'error'}), 26 | new winston.transports.File({ filename: 'debug.log', level: 'debug'}) 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | coverageDirectory: "coverage", 5 | coverageReporters: ["json", "lcov", "text"], 6 | collectCoverageFrom: [ 7 | "lib/**/*.ts" 8 | ], 9 | testPathIgnorePatterns: ["/node_modules/", "/dist/"] 10 | }; 11 | -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/error-plugin/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/lib/__test__/__fixtures__/error-plugin/index.ts -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/error-plugin/plugin.ts: -------------------------------------------------------------------------------- 1 | export class ErrorPlugin { 2 | name: string; 3 | 4 | constructor() { 5 | this.name = "error-plugin"; 6 | } 7 | 8 | // eslint-disable-next-line class-methods-use-this 9 | async init(): Promise { 10 | throw new Error("throw a error"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/error-plugin/tswconfig.ts: -------------------------------------------------------------------------------- 1 | import { ErrorPlugin } from "./plugin"; 2 | 3 | module.exports = { 4 | plugins: [ 5 | new ErrorPlugin() 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/no-plugin/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/lib/__test__/__fixtures__/no-plugin/index.ts -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/no-plugin/tswconfig.ts: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/normal-plugin/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/lib/__test__/__fixtures__/normal-plugin/index.ts -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/normal-plugin/plugin.ts: -------------------------------------------------------------------------------- 1 | export class NormalPlugin { 2 | name: string; 3 | 4 | constructor() { 5 | this.name = "normal-plugin"; 6 | } 7 | 8 | // eslint-disable-next-line class-methods-use-this 9 | async init(): Promise { 10 | // do nothing 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/__test__/__fixtures__/normal-plugin/tswconfig.ts: -------------------------------------------------------------------------------- 1 | import { NormalPlugin } from "./plugin"; 2 | 3 | module.exports = { 4 | plugins: [ 5 | new NormalPlugin() 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /lib/__test__/cli.test.ts: -------------------------------------------------------------------------------- 1 | import tsw from "../index"; 2 | 3 | jest.mock("../index"); 4 | 5 | describe("tsw cli", () => { 6 | it("without any params", async () => { 7 | process.argv.push("-c"); 8 | process.argv.push("a/b/config.js"); 9 | 10 | (tsw as jest.Mock).mockImplementation(() => {}); 11 | 12 | await import("../cli"); 13 | 14 | expect((tsw as jest.Mock).mock.calls[0][2]).toStrictEqual("a/b/config.js"); 15 | 16 | process.argv.pop(); 17 | process.argv.pop(); 18 | }); 19 | 20 | it("params -c", async () => { 21 | process.argv.push("-c"); 22 | process.argv.push("a/b/config.js"); 23 | 24 | (tsw as jest.Mock).mockImplementation(() => {}); 25 | 26 | await import("../cli"); 27 | 28 | expect((tsw as jest.Mock).mock.calls[0][2]).toStrictEqual("a/b/config.js"); 29 | 30 | process.argv.pop(); 31 | process.argv.pop(); 32 | }); 33 | 34 | it("params --config", async () => { 35 | process.argv.push("--config"); 36 | process.argv.push("a/b/config.js"); 37 | 38 | (tsw as jest.Mock).mockImplementation(() => {}); 39 | 40 | await import("../cli"); 41 | 42 | expect((tsw as jest.Mock).mock.calls[0][2]).toStrictEqual("a/b/config.js"); 43 | 44 | process.argv.pop(); 45 | process.argv.pop(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /lib/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import tsw, { uninstallHacks } from "../index"; 2 | import { createServer, get as httpGet } from "http"; 3 | 4 | /** 5 | * 4000 - 5000 random port 6 | */ 7 | const randomPort = (): number => Math.floor(Math.random() * 1000 + 4000); 8 | 9 | let server; 10 | let port; 11 | 12 | const RESPONSE_STRING = "success"; 13 | 14 | beforeAll(() => { 15 | uninstallHacks(); 16 | 17 | port = randomPort(); 18 | server = createServer((req, res) => { 19 | // process.domain in git pipeline is undefined 20 | expect(process.domain).toBeFalsy(); 21 | 22 | res.statusCode = 200; 23 | res.end(RESPONSE_STRING); 24 | }).listen(port); 25 | }); 26 | 27 | afterAll(() => { 28 | server.close(); 29 | uninstallHacks(); 30 | }); 31 | 32 | describe("tsw index", () => { 33 | it("load normal plugin", async () => { 34 | await tsw( 35 | __dirname, 36 | "./__fixtures__/normal-plugin/index.ts", 37 | "./__fixtures__/normal-plugin/tswconfig.ts" 38 | ); 39 | }); 40 | 41 | it("load no plugin", async () => { 42 | await tsw( 43 | __dirname, 44 | "./__fixtures__/no-plugin/index.ts", 45 | "./__fixtures__/no-plugin/tswconfig.ts" 46 | ); 47 | }); 48 | 49 | it("load error plugin", async () => { 50 | const mockExit = jest 51 | .spyOn(process, "exit") 52 | .mockImplementationOnce(() => {}); 53 | 54 | await tsw( 55 | __dirname, 56 | "./__fixtures__/error-plugin/index.ts", 57 | "./__fixtures__/error-plugin/tswconfig.ts" 58 | ); 59 | 60 | expect(mockExit).toHaveBeenCalledWith(-1); 61 | }); 62 | 63 | it("test uninstallHacks", async () => new Promise((resolve) => { 64 | httpGet(`http://127.0.0.1:${port}`, (res) => { 65 | res.on("data", (d) => { 66 | expect(d.toString("utf8")).toBe(RESPONSE_STRING); 67 | resolve(0); 68 | }); 69 | }).end(); 70 | })); 71 | }); 72 | -------------------------------------------------------------------------------- /lib/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as yargs from "yargs"; 4 | import tsw from "./index"; 5 | 6 | const { argv } = yargs 7 | .alias("h", "help") 8 | .option("verbose", { 9 | alias: "v", 10 | type: "boolean", 11 | description: "Run with verbose logging", 12 | default: false 13 | }) 14 | .option("config", { 15 | alias: "c", 16 | type: "string", 17 | description: "Config file path", 18 | default: "tswconfig.js" 19 | }); 20 | 21 | const { _, config } = argv; 22 | const [main] = _; 23 | 24 | tsw(process.cwd(), main, config); 25 | -------------------------------------------------------------------------------- /lib/core/__test__/winston.test.ts: -------------------------------------------------------------------------------- 1 | import * as winston from "winston"; 2 | import { winstonHack, winstonRestore } from "../winston"; 3 | import logger from "../../core/logger/index"; 4 | 5 | global.tswConfig = { 6 | plugins: [], 7 | winstonTransports: undefined 8 | }; 9 | 10 | describe("test winstonHack env", () => { 11 | test("test logger's winstonLogger when it hasn't hacked", () => { 12 | expect(logger.winstonLogger).toBe(undefined); 13 | }); 14 | 15 | test("test winstonHack while config without winstonTransports ", () => { 16 | winstonHack(); 17 | expect(logger.winstonLogger).toBe(undefined); 18 | }); 19 | 20 | test("test winstonHack while the length of winstonTransports is 0", () => { 21 | winstonHack(); 22 | expect(logger.winstonLogger).toBe(undefined); 23 | }); 24 | 25 | test("test winstonHack while config with Console Transports ", () => { 26 | global.tswConfig.winstonTransports = [ 27 | new winston.transports.Console() 28 | ]; 29 | 30 | winstonHack(); 31 | expect(logger.winstonLogger.transports.length).toBe(1); 32 | }); 33 | 34 | test("test logger's winstonLogger when it has restored", () => { 35 | winstonHack(); 36 | winstonRestore(); 37 | expect(logger.winstonLogger).toBe(undefined); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /lib/core/bus.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import { EventEmitter } from "events"; 10 | import * as dns from "dns"; 11 | import * as http from "http"; 12 | import { Context } from "./context"; 13 | 14 | interface ResponseEventPayload { 15 | req: http.IncomingMessage; 16 | res: http.ServerResponse; 17 | context: Context; 18 | } 19 | 20 | interface RequestEventPayload { 21 | req: http.IncomingMessage; 22 | context: Context; 23 | } 24 | 25 | export enum EVENT_LIST { 26 | DNS_LOOKUP_SUCCESS = "DNS_LOOKUP_SUCCESS", 27 | DNS_LOOKUP_ERROR = "DNS_LOOKUP_ERROR", 28 | 29 | /** 30 | * Emitted when then http.ServerResponse begin writeHead() 31 | */ 32 | RESPONSE_START = "RESPONSE_START", 33 | /** 34 | * http.ServerResponse on "finish" event 35 | * 36 | * Emitted when the response has been sent. 37 | * More specifically, this event is emitted when the last segment of the 38 | * response headers and body have been handed off to the operating system 39 | * for transmission over the network. 40 | * It does not imply that the client has received anything yet. 41 | */ 42 | RESPONSE_FINISH = "RESPONSE_FINISH", 43 | /** 44 | * http.ServerResponse on "close" event 45 | * 46 | * Indicates that the underlying connection was terminated. 47 | */ 48 | RESPONSE_CLOSE = "RESPONSE_CLOSE", 49 | 50 | /** 51 | * Emitted when then http.Request coming 52 | */ 53 | REQUEST_START = "REQUEST_START", 54 | } 55 | 56 | interface EventBus extends EventEmitter { 57 | emit( 58 | event: EVENT_LIST.DNS_LOOKUP_SUCCESS, 59 | payload: string | dns.LookupAddress[] 60 | ): boolean; 61 | emit( 62 | event: EVENT_LIST.DNS_LOOKUP_ERROR, 63 | payload: NodeJS.ErrnoException 64 | ): boolean; 65 | emit( 66 | event: EVENT_LIST.RESPONSE_START, 67 | payload: ResponseEventPayload 68 | ): boolean; 69 | emit( 70 | event: EVENT_LIST.RESPONSE_FINISH, 71 | payload: ResponseEventPayload 72 | ): boolean; 73 | emit( 74 | event: EVENT_LIST.RESPONSE_CLOSE, 75 | payload: ResponseEventPayload 76 | ): boolean; 77 | emit( 78 | event: EVENT_LIST.REQUEST_START, 79 | payload: RequestEventPayload 80 | ): boolean; 81 | 82 | on( 83 | event: EVENT_LIST.DNS_LOOKUP_SUCCESS, 84 | listener: (payload: string | dns.LookupAddress[]) => void 85 | ): this; 86 | on( 87 | event: EVENT_LIST.DNS_LOOKUP_ERROR, 88 | listener: (payload: NodeJS.ErrnoException) => void 89 | ): this; 90 | on( 91 | event: EVENT_LIST.RESPONSE_START, 92 | listener: (payload: ResponseEventPayload) => void 93 | ): this; 94 | on( 95 | event: EVENT_LIST.RESPONSE_FINISH, 96 | listener: (payload: ResponseEventPayload) => void 97 | ): this; 98 | on( 99 | event: EVENT_LIST.RESPONSE_CLOSE, 100 | listener: (payload: ResponseEventPayload) => void 101 | ): this; 102 | on( 103 | event: EVENT_LIST.REQUEST_START, 104 | listener: (payload: RequestEventPayload) => void 105 | ): this; 106 | 107 | once( 108 | event: EVENT_LIST.DNS_LOOKUP_SUCCESS, 109 | listener: (payload: string | dns.LookupAddress[]) => void 110 | ): this; 111 | once( 112 | event: EVENT_LIST.DNS_LOOKUP_ERROR, 113 | listener: (payload: NodeJS.ErrnoException) => void 114 | ): this; 115 | once( 116 | event: EVENT_LIST.RESPONSE_START, 117 | listener: (payload: ResponseEventPayload) => void 118 | ): this; 119 | once( 120 | event: EVENT_LIST.RESPONSE_FINISH, 121 | listener: (payload: ResponseEventPayload) => void 122 | ): this; 123 | once( 124 | event: EVENT_LIST.RESPONSE_CLOSE, 125 | listener: (payload: ResponseEventPayload) => void 126 | ): this; 127 | once( 128 | event: EVENT_LIST.REQUEST_START, 129 | listener: (payload: RequestEventPayload) => void 130 | ): this; 131 | } 132 | 133 | let bus: EventBus | undefined; 134 | 135 | export const eventBus: EventBus = bus || (bus = new EventEmitter()); 136 | -------------------------------------------------------------------------------- /lib/core/config.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | export default { 10 | timeout: { 11 | dns: 3000 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/core/context.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | export interface Log { 10 | showLineNumber: boolean; 11 | arr: Array; 12 | 13 | ERROR: number; 14 | WARN: number; 15 | INFO: number; 16 | DEBUG: number; 17 | } 18 | 19 | export interface RequestLog { 20 | SN: number; 21 | 22 | protocol: "HTTPS" | "HTTP"; 23 | host: string; 24 | path: string; 25 | process: string; 26 | 27 | clientIp: string; 28 | clientPort: number; 29 | serverIp: string; 30 | serverPort: number; 31 | 32 | requestHeader: string; 33 | requestBody: string; 34 | 35 | responseHeader: string; 36 | responseBody: string; 37 | responseLength: number; 38 | responseType: string; 39 | statusCode: number; 40 | 41 | timestamps: { 42 | /** 43 | * Request begin. 44 | */ 45 | requestStart: number; 46 | /** 47 | * request.on("socket") 48 | */ 49 | onSocket: number; 50 | /** 51 | * Exact time that dns look up done. 52 | */ 53 | onLookUp: number; 54 | /** 55 | * Exact time that client finished sending HTTP request to the server. 56 | */ 57 | requestFinish: number; 58 | /** 59 | * socket.on("connect") 60 | */ 61 | socketConnect: number; 62 | /** 63 | * request.on("response") 64 | */ 65 | onResponse: number; 66 | /** 67 | * response.on("close") 68 | */ 69 | responseClose: number; 70 | /** 71 | * milliseconds Fiddler spent in DNS looking up the server's IP address. 72 | */ 73 | dnsTime: number; 74 | }; 75 | } 76 | 77 | export class Context { 78 | /** 79 | * Line by line logs for current request/response. 80 | */ 81 | log: Log; 82 | /** 83 | * Serial number for this process. 84 | * Indicates how many this server handled. 85 | */ 86 | SN: number; 87 | /** 88 | * Raw data of current request/response. 89 | */ 90 | currentRequest: RequestLog; 91 | /** 92 | * How many ajax launched by current request. 93 | */ 94 | captureSN: number; 95 | /** 96 | * All ajax raw data. 97 | */ 98 | captureRequests: RequestLog[]; 99 | /** 100 | * Proxy ip for certain request. 101 | */ 102 | proxyIp: string; 103 | /** 104 | * Proxy port for certain request. 105 | */ 106 | proxyPort: number; 107 | /** 108 | * Mark for user. 109 | */ 110 | uid: string; 111 | 112 | constructor() { 113 | this.log = { 114 | showLineNumber: false, 115 | arr: [], 116 | ERROR: 0, 117 | WARN: 0, 118 | INFO: 0, 119 | DEBUG: 0 120 | }; 121 | 122 | process.SN = process.SN || 0; 123 | process.SN += 1; 124 | 125 | this.SN = process.SN; 126 | 127 | this.captureSN = 0; 128 | this.captureRequests = []; 129 | // Empty string is a loopback address in IPV6 130 | this.proxyIp = "NOT_A_IP"; 131 | this.proxyPort = 80; 132 | this.uid = ""; 133 | } 134 | } 135 | 136 | export default (): Context | null => { 137 | if (!process.domain) { 138 | return null; 139 | } 140 | 141 | if (!process.domain.currentContext) { 142 | process.domain.currentContext = new Context(); 143 | } 144 | 145 | return process.domain.currentContext; 146 | }; 147 | -------------------------------------------------------------------------------- /lib/core/logger/__test__/callinfo.test.ts: -------------------------------------------------------------------------------- 1 | import getCallInfo from "../callInfo"; 2 | 3 | describe("callInfo test", () => { 4 | test("callInfo test without arguments", () => { 5 | const res = getCallInfo(); 6 | expect(res.filename).toBe("lib/core/logger/callInfo.ts"); 7 | }); 8 | 9 | test("callInfo test with arguments", () => { 10 | const res = getCallInfo(0); 11 | expect(res.filename).toBe("lib/core/logger/callInfo.ts"); 12 | }); 13 | 14 | test("callInfo test with invalid arguments", () => { 15 | const res = getCallInfo(-1); 16 | expect(res.filename).toBe(""); 17 | expect(res.column).toBe(0); 18 | expect(res.line).toBe(0); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /lib/core/logger/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import logger, { Logger } from "../index"; 2 | import currentContext from "../../context"; 3 | 4 | jest.mock("../../context"); 5 | 6 | (currentContext as jest.Mock).mockReturnValue({ 7 | log: { 8 | showLineNumber: false, 9 | arr: [], 10 | ERROR: 0, 11 | WARN: 0, 12 | INFO: 0, 13 | DEBUG: 0 14 | }, 15 | SN: 0 16 | }); 17 | 18 | const { log } = currentContext(); 19 | 20 | describe("logger test", () => { 21 | test("log could be set by setLogLevel", async () => { 22 | logger.setLogLevel("INFO"); 23 | expect(logger.logLevel).toBe(20); 24 | 25 | logger.debug("TEST DROP LOG IN INFO LEVEL"); 26 | expect(log.INFO).toBe(0); 27 | 28 | logger.setLogLevel("DEBUG"); 29 | }); 30 | 31 | test("log could be set by setCleanLog", async () => { 32 | logger.setCleanLog(true); 33 | expect(logger.getCleanLog()).toBe(true); 34 | 35 | logger.setCleanLog(false); 36 | }); 37 | 38 | test("debug and info could be hided by cleanLog", async () => { 39 | logger.setCleanLog(true); 40 | 41 | logger.debug("TEST DEBUG LOG IN CLEANLOG"); 42 | expect(log.DEBUG).toBe(0); 43 | 44 | logger.info("TEST INFO LOG IN CLEANLOG"); 45 | expect(log.INFO).toBe(0); 46 | 47 | logger.setCleanLog(false); 48 | }); 49 | 50 | test("log could be classified by level", async () => { 51 | logger.debug("TEST DEBUG LOG"); 52 | expect(log.DEBUG).toBe(1); 53 | 54 | logger.info("TEST INFO LOG"); 55 | expect(log.INFO).toBe(1); 56 | 57 | logger.warn("TEST INFO LOG"); 58 | expect(log.WARN).toBe(1); 59 | 60 | logger.error("TEST ERROR LOG"); 61 | expect(log.ERROR).toBe(1); 62 | }); 63 | 64 | test("log could be collected in currentContext", async () => { 65 | logger.info("LOG LENGTH IS CUMULATIVE"); 66 | expect(log.arr.length).toBe(5); 67 | }); 68 | 69 | test("log could be log with color", async () => { 70 | process.env.NODE_OPTIONS = "--inspect=true"; 71 | 72 | logger.error("LOG IS COLORFUL"); 73 | logger.warn("LOG IS COLORFUL"); 74 | logger.info("LOG IS COLORFUL"); 75 | }); 76 | 77 | test("log could be clean", async () => { 78 | Logger.clean(); 79 | expect(log.arr).toBe(null); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /lib/core/logger/callInfo.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | import * as path from "path"; 9 | /** 10 | * 利用 V8 Error.captureStackTrace API 11 | * 实现对调用堆栈的详细追踪 12 | */ 13 | export default (level = 0): { 14 | line: number; 15 | column: number; 16 | filename: string; 17 | } => { 18 | const res = { 19 | line: 0, 20 | column: 0, 21 | filename: "" 22 | }; 23 | 24 | const originPrepareStackTrace = Error.prepareStackTrace; 25 | const originStackTraceLimit = Error.stackTraceLimit; 26 | // Format stack traces to an array of CallSite objects. 27 | // See CallSite object definitions at https://v8.dev/docs/stack-trace-api. 28 | Error.prepareStackTrace = ( 29 | error, 30 | structuredStackTrace 31 | ): NodeJS.CallSite[] => structuredStackTrace; 32 | 33 | Error.stackTraceLimit = 100; 34 | 35 | const err = Object.create(null); 36 | Error.captureStackTrace(err); 37 | const { stack } = err; 38 | 39 | Error.prepareStackTrace = originPrepareStackTrace; 40 | Error.stackTraceLimit = originStackTraceLimit; 41 | 42 | if (typeof stack[level]?.getLineNumber === "function") { 43 | res.line = stack[level].getLineNumber(); 44 | res.column = stack[level].getColumnNumber(); 45 | res.filename = path.relative( 46 | process.cwd(), 47 | // 某些场景下 getFileName() 可能为 undefined 48 | // 比如模块不是通过内置的 require 去加载的,而使用其他手段加载 49 | stack[level].getFileName() || "" 50 | ); 51 | } 52 | 53 | return res; 54 | }; 55 | -------------------------------------------------------------------------------- /lib/core/logger/index.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as moment from "moment"; 10 | import * as chalk from "chalk"; 11 | import * as path from "path"; 12 | 13 | import currentContext, { Log } from "../context"; 14 | import isLinux from "../util/isLinux"; 15 | import isInspect from "../util/isInspect"; 16 | import getCallInfo from "./callInfo"; 17 | import { Stream } from "stream"; 18 | import { config as winstonConfig, Logger as WinstonLogger } from "winston"; 19 | 20 | enum LOG_LEVEL { 21 | "DEBUG" = 10, 22 | "INFO" = 20, 23 | "WARN" = 30, 24 | "ERROR"= 40, 25 | } 26 | 27 | enum LOG_COLOR { 28 | "DEBUG" = "yellow", 29 | "INFO" = "blue", 30 | "WARN" = "magenta", 31 | "ERROR"= "red", 32 | } 33 | 34 | type LogLevelStrings = keyof typeof LOG_LEVEL; 35 | 36 | type WinstonLogLevel = keyof typeof winstonConfig.syslog.levels; 37 | 38 | export class Logger { 39 | private isCleanLog = false; 40 | 41 | public logLevel: number 42 | 43 | public winstonLogger: WinstonLogger 44 | 45 | public setLogLevel(level: LogLevelStrings = "DEBUG"): number { 46 | this.logLevel = LOG_LEVEL[level]; 47 | return this.logLevel; 48 | } 49 | 50 | public setCleanLog(isCleanLog = false): void { 51 | this.isCleanLog = isCleanLog; 52 | } 53 | 54 | public getCleanLog(): boolean { 55 | return this.isCleanLog; 56 | } 57 | 58 | public debug(str: string): void { 59 | if (this.isCleanLog) return; 60 | if (!currentContext()) { 61 | console.log(Logger.formatStr(str, "DEBUG", { 62 | levelLimit: this.logLevel 63 | })); 64 | } else { 65 | this.writeLog("DEBUG", str); 66 | } 67 | } 68 | 69 | public info(str: string): void { 70 | if (this.isCleanLog) return; 71 | if (!currentContext()) { 72 | console.info(Logger.formatStr(str, "INFO", { 73 | levelLimit: this.logLevel 74 | })); 75 | } else { 76 | this.writeLog("INFO", str); 77 | } 78 | } 79 | 80 | public warn(str: string): void { 81 | if (!currentContext()) { 82 | console.warn(Logger.formatStr(str, "WARN", { 83 | levelLimit: this.logLevel 84 | })); 85 | } else { 86 | this.writeLog("WARN", str); 87 | } 88 | } 89 | 90 | public error(str: string): void { 91 | if (!currentContext()) { 92 | console.error(Logger.formatStr(str, "ERROR", { 93 | levelLimit: this.logLevel 94 | })); 95 | } else { 96 | this.writeLog("ERROR", str); 97 | } 98 | } 99 | 100 | public static clean(): void { 101 | let log = Logger.getLog(); 102 | if (log) { 103 | log.arr = null; 104 | log = null; 105 | } 106 | } 107 | 108 | public writeLog(type: LogLevelStrings, str: string): void { 109 | const level = LOG_LEVEL[type]; 110 | 111 | // Drop log 112 | if (level < this.logLevel) { 113 | return; 114 | } 115 | 116 | const logStr = Logger.formatStr(str, type, { 117 | levelLimit: this.logLevel 118 | }); 119 | 120 | // Store log 121 | Logger.fillBuffer(type, logStr); 122 | 123 | if (this.winstonLogger) { 124 | const winstonLogType = Logger.getWinstonType(type); 125 | this.winstonLogger.log(`${winstonLogType}`, logStr); 126 | } 127 | 128 | if (isInspect()) { 129 | // When started with inspect, log will send to 2 places 130 | // 1. Local stdout 131 | // 2. Remote(maybe chrome inspect window) inspect window 132 | 133 | // Here for remote window 134 | Logger.fillInspect(logStr, level); 135 | 136 | const logWithColor = Logger.formatStr(str, type, { 137 | levelLimit: this.logLevel, 138 | color: true 139 | }); 140 | 141 | // Here for local stdout, with color 142 | Logger.fillStdout(logWithColor); 143 | } else { 144 | // Send to local stdout 145 | Logger.fillStdout(logStr); 146 | } 147 | } 148 | 149 | /** 150 | * Convert TSW log level to winston log level 151 | * @param type Type of tsw log level 152 | */ 153 | private static getWinstonType(type: LogLevelStrings): WinstonLogLevel { 154 | const logType = type.toLowerCase(); 155 | const winstonLogLevel = winstonConfig.syslog.levels; 156 | if (winstonLogLevel[logType]) { 157 | return logType; 158 | } 159 | 160 | /** 161 | * Take the least important level from Winston syslog levels 162 | */ 163 | const levels = Object.keys(winstonLogLevel); 164 | return levels[levels.length - 1]; 165 | } 166 | 167 | /** 168 | * Format a string based on it's type(DEBUG/INFO/...) 169 | * @param str String need to be formatted 170 | * @param type Log level of this string 171 | * @param options Options 172 | * @param options.levelLimit Log level limit, log will be dropped when not match it 173 | * @param options.color Add ANSI color or not 174 | */ 175 | private static formatStr( 176 | str: string, 177 | type: LogLevelStrings, 178 | options: { 179 | levelLimit: number; 180 | color?: boolean; 181 | } 182 | ): string { 183 | const { levelLimit, color } = options; 184 | 185 | let showLineNumber = false; 186 | let SN = -1; 187 | 188 | if (currentContext()) { 189 | ({ showLineNumber } = Logger.getLog()); 190 | ({ SN } = currentContext()); 191 | } 192 | 193 | const needCallInfoDetail = (LOG_LEVEL[type] >= levelLimit 194 | && showLineNumber) 195 | || !isLinux; 196 | 197 | const timestamp = moment(new Date()).format("YYYY-MM-DD HH:mm:ss.SSS"); 198 | const logType = `[${type}]`; 199 | const pidInfo = `[${process.pid} ${SN}]`; 200 | const callInfo = ((): string => { 201 | if (!needCallInfoDetail) return ""; 202 | // Magic number: 5 203 | // ./lib/core/logger/callInfo.js [exports.default] 204 | // ./lib/core/logger/index.js [THIS anonymous function] 205 | // ./lib/core/logger/index.js [formatStr] 206 | // ./lib/core/logger/index.js [writeLog] 207 | // ./lib/core/runtime/console.hack.js [console.log] 208 | // User called here 209 | const { column, line, filename } = getCallInfo(5); 210 | return `[${filename.split(path.sep).join("/")}:${line}:${column}]`; 211 | })(); 212 | 213 | if (color) { 214 | const typeColor = LOG_COLOR[type]; 215 | return `${chalk.whiteBright(timestamp)} ${chalk[typeColor](logType)} ${ 216 | chalk.whiteBright(pidInfo) 217 | } ${chalk.blueBright(callInfo)} ${str}`; 218 | } 219 | 220 | return `${timestamp} ${logType} ${pidInfo} ${callInfo} ${str}`; 221 | } 222 | 223 | private static getLog(): Log | undefined { 224 | if (!currentContext()) { 225 | return undefined; 226 | } 227 | 228 | const { log } = currentContext(); 229 | return log; 230 | } 231 | 232 | private static fillBuffer(type: string, logStr: string): void { 233 | const log = Logger.getLog(); 234 | if (log) { 235 | if (!log.arr) { 236 | log.arr = []; 237 | } 238 | 239 | if (logStr) { 240 | log.arr.push(logStr); 241 | } 242 | 243 | if (type) { 244 | if (log[type]) { 245 | log[type] += 1; 246 | } else { 247 | log[type] = 1; 248 | } 249 | } 250 | } 251 | } 252 | 253 | private static fillInspect(str: string, level: number): void { 254 | if ((console as any)._stdout === process.stdout) { 255 | const empty = new Stream.Writable(); 256 | empty.write = (): boolean => false; 257 | empty.end = (): void => {}; 258 | (console as any)._stdout = empty; 259 | (console as any)._stderr = empty; 260 | } 261 | /* eslint-enable */ 262 | 263 | if (level <= 20) { 264 | (console.originLog || console.log)(str); 265 | } else if (level <= 30) { 266 | (console.originWarn || console.warn)(str); 267 | } else { 268 | (console.originError || console.error)(str); 269 | } 270 | } 271 | 272 | private static fillStdout(str: string): void { 273 | // console hacking origin write, so use originWrite 274 | const stdout = (process.stdout as any).originWrite || process.stdout.write; 275 | stdout.call(process.stdout, `${str}\n`); 276 | } 277 | } 278 | 279 | let logger: Logger; 280 | if (!logger) { 281 | logger = new Logger(); 282 | } 283 | 284 | export default logger; 285 | -------------------------------------------------------------------------------- /lib/core/runtime/__test__/console.hack.test.ts: -------------------------------------------------------------------------------- 1 | import { consoleHack, consoleRestore } from "../console.hack"; 2 | import logger from "../../logger/index"; 3 | import getCurrentContext from "../../context"; 4 | 5 | jest.mock("../../logger/index"); 6 | jest.mock("../../context"); 7 | 8 | beforeAll(() => { 9 | consoleHack(); 10 | }); 11 | 12 | afterAll(() => { 13 | consoleRestore(); 14 | }); 15 | 16 | afterEach(() => { 17 | jest.clearAllMocks(); 18 | }); 19 | 20 | describe("console hack test", () => { 21 | test("ensure console contains all origin functions", () => { 22 | expect(typeof console.originDebug).toBe("function"); 23 | expect(typeof console.originLog).toBe("function"); 24 | expect(typeof console.originInfo).toBe("function"); 25 | expect(typeof console.originDir).toBe("function"); 26 | expect(typeof console.originWarn).toBe("function"); 27 | expect(typeof console.originError).toBe("function"); 28 | }); 29 | 30 | test("console.debug should be logged by logger", () => { 31 | (getCurrentContext as jest.Mock).mockImplementation(() => true); 32 | const mockedWriteLog = logger.writeLog as jest.Mock; 33 | 34 | expect(mockedWriteLog.mock.calls.length).toEqual(0); 35 | console.debug("test_log"); 36 | expect(mockedWriteLog.mock.calls.length).toEqual(1); 37 | console.log("test_log"); 38 | expect(mockedWriteLog.mock.calls.length).toEqual(2); 39 | console.info("test_log"); 40 | expect(mockedWriteLog.mock.calls.length).toEqual(3); 41 | console.dir("test_log"); 42 | expect(mockedWriteLog.mock.calls.length).toEqual(4); 43 | console.warn("test_log"); 44 | expect(mockedWriteLog.mock.calls.length).toEqual(5); 45 | console.error("test_log"); 46 | expect(mockedWriteLog.mock.calls.length).toEqual(6); 47 | 48 | (getCurrentContext as jest.Mock).mockClear(); 49 | }); 50 | 51 | test("console.debug should be logged by log111ger", () => { 52 | (getCurrentContext as jest.Mock).mockImplementation(() => true); 53 | const mockedWriteLog = logger.writeLog as jest.Mock; 54 | 55 | expect(mockedWriteLog.mock.calls.length).toEqual(0); 56 | process.stdout.write("test_log"); 57 | expect(mockedWriteLog.mock.calls.length).toEqual(1); 58 | process.stderr.write("test_log"); 59 | expect(mockedWriteLog.mock.calls.length).toEqual(2); 60 | 61 | (getCurrentContext as jest.Mock).mockClear(); 62 | }); 63 | 64 | test("multi consoleRestore() should not have side effect", () => { 65 | consoleRestore(); 66 | consoleRestore(); 67 | }); 68 | 69 | test("multi consoleHack() should not have side effect", () => { 70 | consoleHack(); 71 | consoleHack(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /lib/core/runtime/__test__/create-server.test.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import { createServer, get as httpGet, request as httpRequest } from "http"; 10 | import { 11 | httpCreateServerHack, 12 | httpsCreateServerHack, 13 | httpCreateServerRestore, 14 | httpsCreateServerRestore 15 | } from "../create-server.hack"; 16 | import { eventBus, EVENT_LIST } from "../../bus"; 17 | 18 | beforeAll(() => { 19 | httpCreateServerHack(); 20 | httpsCreateServerHack(); 21 | }); 22 | 23 | afterAll(() => { 24 | httpCreateServerRestore(); 25 | httpsCreateServerRestore(); 26 | }); 27 | 28 | /** 29 | * 4000 - 5000 random port 30 | */ 31 | const randomPort = (): number => Math.floor(Math.random() * 1000 + 4000); 32 | const randomString = (): string => Math.random().toString(36).substring(2, 15) 33 | + Math.random().toString(36).substring(2, 15); 34 | 35 | describe("http createServer hack test", () => { 36 | test("createServer with 1 params", async () => { 37 | const port = randomPort(); 38 | const path = randomString(); 39 | 40 | const server = createServer((req, res) => { 41 | expect(process.domain).not.toBeNull(); 42 | res.statusCode = 200; 43 | res.end("success"); 44 | }).listen(port); 45 | 46 | return new Promise((resolve, reject) => { 47 | httpGet(`http://127.0.0.1:${port}/${path}`, (res) => { 48 | // Close server first 49 | server.close(); 50 | 51 | if (res.statusCode === 200) resolve(0); 52 | else reject(); 53 | }); 54 | }); 55 | }); 56 | 57 | test("createServer with 2 params", async () => { 58 | const port = randomPort(); 59 | const path = randomString(); 60 | 61 | const server = createServer({}, (req, res) => { 62 | expect(process.domain).not.toBeNull(); 63 | res.setHeader("x-test-res", "test"); 64 | res.statusCode = 200; 65 | res.end("success"); 66 | }).listen(port); 67 | 68 | return new Promise((resolve, reject) => { 69 | httpGet(`http://127.0.0.1:${port}/${path}`, (res) => { 70 | // Close server first 71 | server.close(); 72 | 73 | if (res.statusCode === 200) resolve(0); 74 | else reject(); 75 | }); 76 | }); 77 | }); 78 | 79 | test("proxy get", async () => { 80 | const path = randomString(); 81 | 82 | const proxyServerPort = randomPort(); 83 | const realServerPort = randomPort(); 84 | 85 | const requestStartListener = ({ context }): void => { 86 | context.proxyIp = "127.0.0.1"; 87 | context.proxyPort = realServerPort; 88 | }; 89 | 90 | eventBus.on(EVENT_LIST.REQUEST_START, requestStartListener); 91 | 92 | const realServer = createServer((req, res) => { 93 | res.statusCode = 200; 94 | res.end("real server response"); 95 | }).listen(realServerPort); 96 | 97 | const proxyServer = createServer((req, res) => { 98 | res.statusCode = 200; 99 | res.end("proxy server response"); 100 | }).listen(proxyServerPort); 101 | 102 | await new Promise((resolve) => { 103 | httpGet(`http://127.0.0.1:${proxyServerPort}/${path}`, (res) => { 104 | // Close server first 105 | proxyServer.close(); 106 | realServer.close(); 107 | 108 | let body = ""; 109 | 110 | res.on("data", (d) => { 111 | body += d.toString("utf8"); 112 | }); 113 | 114 | res.on("end", () => { 115 | expect(body).toBe("real server response"); 116 | resolve(0); 117 | }); 118 | }); 119 | }); 120 | 121 | eventBus.off(EVENT_LIST.REQUEST_START, requestStartListener); 122 | }); 123 | 124 | test("proxy post", async () => { 125 | const path = randomString(); 126 | 127 | const proxyServerPort = randomPort(); 128 | const realServerPort = randomPort(); 129 | 130 | const requestStartListener = ({ context }): void => { 131 | context.proxyIp = "127.0.0.1"; 132 | context.proxyPort = realServerPort; 133 | }; 134 | 135 | eventBus.on(EVENT_LIST.REQUEST_START, requestStartListener); 136 | 137 | const realServer = createServer((req, res) => { 138 | res.statusCode = 200; 139 | res.end("real server response"); 140 | }).listen(realServerPort); 141 | 142 | const proxyServer = createServer((req, res) => { 143 | res.statusCode = 200; 144 | res.end("proxy server response"); 145 | }).listen(proxyServerPort); 146 | 147 | await new Promise((resolve) => { 148 | httpRequest(`http://127.0.0.1:${proxyServerPort}/${path}`, { 149 | method: "POST" 150 | }, (res) => { 151 | // Close server first 152 | proxyServer.close(); 153 | realServer.close(); 154 | 155 | let body = ""; 156 | 157 | res.on("data", (d) => { 158 | body += d.toString("utf8"); 159 | }); 160 | 161 | res.on("end", () => { 162 | expect(body).toBe("real server response"); 163 | resolve(0); 164 | }); 165 | }).end(); 166 | }); 167 | 168 | eventBus.off(EVENT_LIST.REQUEST_START, requestStartListener); 169 | }); 170 | 171 | test("proxy to a non-exist server", async () => { 172 | const path = randomString(); 173 | 174 | const proxyServerPort = randomPort(); 175 | 176 | const requestStartListener = ({ context }): void => { 177 | context.proxyIp = "127.0.0.1"; 178 | // A non-exists port 179 | context.proxyPort = 10; 180 | }; 181 | 182 | eventBus.on(EVENT_LIST.REQUEST_START, requestStartListener); 183 | 184 | const proxyServer = createServer((req, res) => { 185 | res.statusCode = 200; 186 | res.end("proxy server response"); 187 | }).listen(proxyServerPort); 188 | 189 | await new Promise((resolve) => { 190 | httpGet(`http://127.0.0.1:${proxyServerPort}/${path}`, (res) => { 191 | // Close server first 192 | proxyServer.close(); 193 | 194 | let body = ""; 195 | 196 | res.on("data", (d) => { 197 | body += d.toString("utf8"); 198 | }); 199 | 200 | res.on("end", () => { 201 | expect(res.statusCode).toBe(500); 202 | expect(body).toBe(""); 203 | resolve(0); 204 | }); 205 | }); 206 | }); 207 | 208 | eventBus.off(EVENT_LIST.REQUEST_START, requestStartListener); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /lib/core/runtime/__test__/dns.hack.test.ts: -------------------------------------------------------------------------------- 1 | import { lookup } from "dns"; 2 | import { isIP } from "net"; 3 | 4 | import { eventBus, EVENT_LIST } from "../../bus"; 5 | import { dnsHack, dnsRestore } from "../dns.hack"; 6 | import logger from "../../logger/index"; 7 | 8 | jest.mock("../../logger/index"); 9 | 10 | (logger.debug as jest.Mock).mockImplementation(() => {}); 11 | (logger.info as jest.Mock).mockImplementation(() => {}); 12 | (logger.warn as jest.Mock).mockImplementation(() => {}); 13 | (logger.error as jest.Mock).mockImplementation(() => {}); 14 | 15 | beforeAll(() => { 16 | dnsHack(); 17 | }); 18 | 19 | afterAll(() => { 20 | dnsRestore(); 21 | }); 22 | 23 | describe("dns hack test", () => { 24 | test("dns could work normally", async () => { 25 | await new Promise((resolve) => { 26 | lookup("qq.com", (err, address, family) => { 27 | expect(err).toBeNull(); 28 | expect(isIP(address)).toBeTruthy(); 29 | expect(family).toEqual(4); 30 | resolve(0); 31 | }); 32 | }); 33 | }); 34 | 35 | test("dns could work with options normally", async () => { 36 | await new Promise((resolve) => { 37 | const options = { family: 4 }; 38 | lookup("qq.com", options, (err, address, family) => { 39 | expect(err).toBeNull(); 40 | expect(isIP(address)).toBeTruthy(); 41 | expect(family).toEqual(4); 42 | resolve(0); 43 | }); 44 | }); 45 | }); 46 | 47 | test("eventBus was informed", async () => { 48 | await new Promise((resolve, reject) => { 49 | eventBus.on(EVENT_LIST.DNS_LOOKUP_SUCCESS, (data) => { 50 | resolve(data); 51 | }); 52 | 53 | eventBus.on(EVENT_LIST.DNS_LOOKUP_ERROR, (err) => { 54 | reject(err); 55 | }); 56 | 57 | lookup("qq.com", () => { 58 | // nothing 59 | }); 60 | }); 61 | }); 62 | 63 | test("ipv4 should return immediately", async () => { 64 | await new Promise((resolve) => { 65 | const ip = "1.2.3.4"; 66 | lookup(ip, (err, address, family) => { 67 | expect(err).toBeNull(); 68 | expect(address).toEqual(ip); 69 | expect(family).toEqual(4); 70 | resolve(0); 71 | }); 72 | }); 73 | }); 74 | 75 | test("ipv4 should return with options immediately", async () => { 76 | await new Promise((resolve) => { 77 | const ip = "1.2.3.4"; 78 | const options = { family: 4 }; 79 | lookup(ip, options, (err, address, family) => { 80 | expect(err).toBeNull(); 81 | expect(address).toEqual(ip); 82 | expect(family).toEqual(4); 83 | resolve(0); 84 | }); 85 | }); 86 | }); 87 | 88 | test("ipv6 should return immediately", async () => { 89 | await new Promise((resolve) => { 90 | const ip = "::ffff:192.0.2.128"; 91 | lookup(ip, (err, address, family) => { 92 | expect(err).toBeNull(); 93 | expect(address).toEqual(ip); 94 | expect(family).toEqual(6); 95 | resolve(0); 96 | }); 97 | }); 98 | }); 99 | 100 | test("a wrong domain should fail", async () => { 101 | await new Promise((resolve) => { 102 | const nullDomain = "this is not a domain"; 103 | lookup(nullDomain, (err) => { 104 | // error could be "Dns Lookup Timeout" 105 | // or "getaddrinfo ENOTFOUND this is not a domain" 106 | expect(err).toBeTruthy(); 107 | resolve(0); 108 | }); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/__test__/incoming.test.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as http from "http"; 10 | import { 11 | captureIncoming, 12 | captureReadableStream 13 | } from "../incoming"; 14 | import { Readable } from "stream"; 15 | 16 | /** 17 | * 4000 - 5000 random port 18 | */ 19 | const randomPort = (): number => Math.floor(Math.random() * 1000 + 4000); 20 | 21 | let server: http.Server; 22 | let port: number; 23 | 24 | const responseData = "success"; 25 | 26 | beforeAll(() => { 27 | port = randomPort(); 28 | server = http.createServer((req, res) => { 29 | res.statusCode = 200; 30 | res.end(responseData); 31 | }).listen(port); 32 | }); 33 | 34 | afterAll(() => { 35 | server.close(); 36 | }); 37 | 38 | describe("capture response function test", () => { 39 | test("response data should be captured", (done) => { 40 | const data = "a"; 41 | let info: any; 42 | 43 | const req = http.request({ 44 | hostname: "127.0.0.1", 45 | port, 46 | path: "/", 47 | method: "POST", 48 | headers: { 49 | Connection: "Close", 50 | "Content-Type": "application/x-www-form-urlencoded", 51 | "Content-Length": Buffer.byteLength(data) 52 | } 53 | }, (response) => { 54 | info = captureIncoming(response); 55 | }); 56 | 57 | req.write(data); 58 | 59 | req.once("close", () => { 60 | expect(info.body).toEqual(Buffer.from(responseData)); 61 | expect(info.bodyLength).toEqual(Buffer.byteLength(responseData)); 62 | done(); 63 | }); 64 | }); 65 | 66 | test("capture readableStream should be right", (done) => { 67 | const stream = new Readable(); 68 | 69 | stream.push("before"); 70 | const info = captureReadableStream(stream); 71 | 72 | stream.push("after"); 73 | stream.destroy(); 74 | 75 | stream.on("close", () => { 76 | expect(info.bodyLength).toEqual(Buffer.byteLength("beforeafter")); 77 | expect(info.body).toEqual(Buffer.from("beforeafter")); 78 | expect(info.bodyTooLarge).toEqual(false); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | import { requestHack, requestRestore } from "../index"; 3 | 4 | /** 5 | * 4000 - 5000 random port 6 | */ 7 | const randomPort = (): number => Math.floor(Math.random() * 1000 + 4000); 8 | 9 | let server: http.Server; 10 | let port: number; 11 | 12 | const RESPONSE_STRING = "success"; 13 | 14 | beforeAll(() => { 15 | requestHack(); 16 | 17 | port = randomPort(); 18 | server = http.createServer((req, res) => { 19 | res.statusCode = 200; 20 | res.end(RESPONSE_STRING); 21 | }).listen(port); 22 | }); 23 | 24 | afterAll(() => { 25 | requestRestore(); 26 | 27 | server.close(); 28 | }); 29 | 30 | describe("capture(request hack) test", () => { 31 | test("http request", async () => { 32 | await new Promise((resolve) => { 33 | http.request(`http://127.0.0.1:${port}`, (res) => { 34 | res.on("data", (d) => { 35 | expect(d.toString("utf8")).toBe(RESPONSE_STRING); 36 | resolve(0); 37 | }); 38 | }).end(); 39 | }); 40 | }); 41 | 42 | test("http request", async () => { 43 | await new Promise((resolve) => { 44 | http.request({ 45 | protocol: "http:", 46 | host: "127.0.0.1", 47 | port 48 | }, (res) => { 49 | res.on("data", (d) => { 50 | expect(d.toString("utf8")).toBe(RESPONSE_STRING); 51 | resolve(0); 52 | }); 53 | }).end(); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/__test__/outgoing.test.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as http from "http"; 10 | import { captureOutgoing } from "../outgoing"; 11 | 12 | /** 13 | * 4000 - 5000 random port 14 | */ 15 | const randomPort = (): number => Math.floor(Math.random() * 1000 + 4000); 16 | 17 | let server: http.Server; 18 | let port: number; 19 | 20 | beforeAll(() => { 21 | port = randomPort(); 22 | server = http.createServer((req, res) => { 23 | res.statusCode = 200; 24 | res.end("success"); 25 | }).listen(port); 26 | }); 27 | 28 | afterAll(() => { 29 | server.close(); 30 | }); 31 | 32 | describe("capture request function test", () => { 33 | test("request should capture post data", (done) => { 34 | const firstData = "a"; 35 | const secondData = "b"; 36 | const thirdData = "c"; 37 | const data = firstData + secondData + thirdData; 38 | 39 | const req = http.request({ 40 | hostname: "127.0.0.1", 41 | port, 42 | path: "/", 43 | method: "POST", 44 | headers: { 45 | "Content-Type": "application/x-www-form-urlencoded", 46 | "Content-Length": Buffer.byteLength(data) 47 | } 48 | }, () => { 49 | expect((req as any)._body).toBeTruthy(); 50 | expect((req as any)._body.toString()).toEqual(data); 51 | expect((req as any)._bodyLength).toEqual(Buffer.byteLength(data)); 52 | expect((req as any)._bodyTooLarge).toEqual(false); 53 | done(); 54 | }); 55 | 56 | captureOutgoing(req); 57 | 58 | req.write(firstData); 59 | // Write data to request body 60 | req.write(Buffer.from(secondData, "utf8"), "utf8", () => { 61 | // do nothing 62 | }); 63 | 64 | req.write(Buffer.from(thirdData), () => { 65 | // do nothing 66 | }); 67 | 68 | req.end(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/incoming.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | import * as http from "http"; 9 | 10 | // Max response body size 11 | const maxBodySize = 512 * 1024; 12 | 13 | export interface ResponseBodyInfo { 14 | bodyLength: number; 15 | bodyChunks: Buffer[]; 16 | body: Buffer; 17 | bodyTooLarge: boolean; 18 | } 19 | 20 | export const captureReadableStream = ( 21 | stream: NodeJS.ReadableStream 22 | ): ResponseBodyInfo => { 23 | const originPush = (stream as any).push; 24 | 25 | const info: ResponseBodyInfo = { 26 | bodyLength: 0, 27 | bodyChunks: [], 28 | bodyTooLarge: false, 29 | body: Buffer.alloc(0) 30 | }; 31 | 32 | Object.defineProperty(info, "body", { 33 | // 需要用的时候才拼接buffer 34 | get: () => Buffer.concat(info.bodyChunks) 35 | }); 36 | 37 | const handler = (chunk: any): void => { 38 | info.bodyLength += Buffer.byteLength(chunk); 39 | // 到达最大限制后,不再记录回包内容 40 | if (info.bodyTooLarge) { 41 | return; 42 | } 43 | 44 | info.bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); 45 | info.bodyTooLarge = info.bodyLength > maxBodySize; 46 | }; 47 | 48 | const rb = (stream as any).readableBuffer; 49 | let { head } = rb; 50 | if (head !== undefined) { 51 | while (head) { 52 | handler(head.data); 53 | head = head.next; 54 | } 55 | } else if (rb.forEach) { 56 | rb.forEach((c) => { 57 | handler(c); 58 | }); 59 | } 60 | 61 | (stream as any).push = (chunk: any, encoding?: string): boolean => { 62 | if (chunk) { 63 | handler(chunk); 64 | } 65 | 66 | return originPush.call(stream, chunk, encoding); 67 | }; 68 | 69 | return info; 70 | }; 71 | 72 | export const captureIncoming = ( 73 | response: http.IncomingMessage 74 | ): ResponseBodyInfo => captureReadableStream(response); 75 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/index.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as http from "http"; 10 | import * as https from "https"; 11 | import * as domain from "domain"; 12 | import { URL } from "url"; 13 | import { Socket, isIP } from "net"; 14 | import { cloneDeep } from "lodash"; 15 | import { captureOutgoing } from "./outgoing"; 16 | import { captureIncoming } from "./incoming"; 17 | 18 | import currentContext, { RequestLog, Context } from "../../context"; 19 | import logger from "../../logger/index"; 20 | 21 | type requestProtocol = "http:" | "https:"; 22 | 23 | /** 24 | * Convert a URL instance to a http.request options 25 | * https://github.com/nodejs/node/blob/afa9a7206c26a29a2af226696c145c924a6d3754/lib/internal/url.js#L1270 26 | * @param url a URL instance 27 | */ 28 | const urlToOptions = (url: URL): http.RequestOptions => { 29 | const options: http.RequestOptions = { 30 | protocol: url.protocol, 31 | hostname: typeof url.hostname === "string" && url.hostname.startsWith("[") 32 | ? url.hostname.slice(1, -1) 33 | : url.hostname, 34 | path: `${url.pathname || ""}${url.search || ""}` 35 | }; 36 | 37 | if (url.port !== "") { 38 | options.port = Number(url.port); 39 | } 40 | 41 | if (url.username || url.password) { 42 | options.auth = `${url.username}:${url.password}`; 43 | } 44 | 45 | return options; 46 | }; 47 | 48 | export const hack = ( 49 | originRequest: T, 50 | protocol: requestProtocol 51 | ): ((...args: unknown[]) => http.ClientRequest) => ( 52 | (...args): http.ClientRequest => { 53 | let options: http.RequestOptions; 54 | if (typeof args[1] === "undefined" || typeof args[1] === "function") { 55 | // function request(options: RequestOptions | string | URL, callback?: (res: IncomingMessage) => void): ClientRequest; 56 | if (typeof args[0] === "string") { 57 | options = urlToOptions(new URL(args[0])); 58 | } else if (args[0] instanceof URL) { 59 | options = urlToOptions(args[0]); 60 | } else { 61 | options = args[0] as http.RequestOptions; 62 | } 63 | } else { 64 | // function request(url: string | URL, options: RequestOptions, callback?: (res: IncomingMessage) => void): ClientRequest; 65 | if (typeof args[0] === "string") { 66 | options = urlToOptions(new URL(args[0])); 67 | } else { 68 | options = urlToOptions(args[0] as URL); 69 | } 70 | 71 | options = Object.assign(options, args[1]); 72 | } 73 | 74 | // Execute request 75 | const request: http.ClientRequest = originRequest.apply(this, args); 76 | // Execute capture,ClientRequest extends OutgoingMessage(extends Stream.Writable) 77 | captureOutgoing(request); 78 | 79 | const context = currentContext() || new Context(); 80 | const logPre = `[${context.captureSN}]`; 81 | 82 | const { 83 | method, host: reqHost, hostname = reqHost, path, port 84 | } = options; 85 | 86 | logger.debug(`${logPre} Request begin. ${ 87 | method} ${hostname}${port ? `:${port}` : ""} ~ ${path}`); 88 | 89 | const requestLog: Partial = { 90 | SN: context.captureSN, 91 | 92 | protocol: protocol === "http:" ? "HTTP" : "HTTPS", 93 | host: hostname, 94 | path, 95 | 96 | process: `TSW: ${process.pid}`, 97 | timestamps: {} as RequestLog["timestamps"] 98 | }; 99 | 100 | const { timestamps } = requestLog; 101 | timestamps.requestStart = new Date().getTime(); 102 | 103 | const clearDomain = (): void => { 104 | const parser = (request.socket as any)?.parser as any; 105 | if (parser && parser.domain) { 106 | (parser.domain as domain.Domain).exit(); 107 | parser.domain = null; 108 | } 109 | }; 110 | 111 | const finishRequest = (): void => { 112 | context.captureRequests.push(requestLog as RequestLog); 113 | 114 | logger.debug(`${logPre} Record request info. Response body length: ${ 115 | requestLog.responseLength 116 | }`); 117 | }; 118 | 119 | request.once("socket", (socket: Socket): void => { 120 | timestamps.onSocket = new Date().getTime(); 121 | 122 | if (!isIP(hostname)) { 123 | socket.once("lookup", ( 124 | err: Error, 125 | address: string, 126 | family: string | number, 127 | host: string 128 | ): void => { 129 | timestamps.onLookUp = new Date().getTime(); 130 | timestamps.dnsTime = timestamps.onLookUp - timestamps.onSocket; 131 | 132 | logger.debug(`${logPre} Dns lookup ${host} -> ${ 133 | address || "null"}. Cost ${timestamps.dnsTime}ms`); 134 | 135 | if (err) { 136 | if (logger.getCleanLog()) { 137 | logger.error(`${logPre} Request: 138 | ${JSON.stringify(requestLog)}`); 139 | } 140 | 141 | logger.error(`${logPre} Lookup ${host} -> ${ 142 | address || "null"}, error ${err.stack}`); 143 | } 144 | }); 145 | } 146 | 147 | socket.once("connect", (): void => { 148 | timestamps.socketConnect = new Date().getTime(); 149 | 150 | logger.debug(`${logPre} Socket connected. Remote: ${ 151 | socket.remoteAddress 152 | }:${socket.remotePort}. Cost ${ 153 | timestamps.socketConnect - timestamps.onSocket 154 | } ms`); 155 | }); 156 | 157 | if (socket.remoteAddress) { 158 | timestamps.dnsTime = 0; 159 | 160 | logger.debug(`${logPre} Socket reused. Remote: ${ 161 | socket.remoteAddress 162 | }:${socket.remotePort}`); 163 | } 164 | }); 165 | 166 | request.once("error", (error: Error) => { 167 | if (logger.getCleanLog()) { 168 | logger.error(`${logPre} Request: ${JSON.stringify(requestLog)}`); 169 | } 170 | 171 | logger.error(`${logPre} Request error. Stack: ${error.stack}`); 172 | finishRequest(); 173 | clearDomain(); 174 | }); 175 | 176 | request.once("close", clearDomain); 177 | 178 | request.once("finish", () => { 179 | timestamps.requestFinish = new Date().getTime(); 180 | 181 | context.captureSN += 1; 182 | 183 | let requestBody: string; 184 | const length = (request as any)._bodyLength; 185 | const tooLarge = (request as any)._bodyTooLarge; 186 | if (tooLarge) { 187 | requestBody = Buffer.from(`body was too large too show, length: ${ 188 | length}`).toString("base64"); 189 | } else { 190 | requestBody = (request as any)._body.toString("base64"); 191 | } 192 | 193 | requestLog.requestHeader = (request as any)._header; 194 | requestLog.requestBody = requestBody; 195 | logger.debug(`${logPre} Request send finish. Body size ${ 196 | length 197 | }. Cost: ${ 198 | timestamps.requestFinish - timestamps.onSocket 199 | } ms`); 200 | 201 | clearDomain(); 202 | }); 203 | 204 | request.once("response", (response: http.IncomingMessage): void => { 205 | timestamps.onResponse = new Date().getTime(); 206 | 207 | const { socket } = response; 208 | requestLog.serverIp = socket.remoteAddress; 209 | requestLog.serverPort = socket.remotePort; 210 | // This could be undefined 211 | // https://stackoverflow.com/questions/16745745/nodejs-tcp-socket-does-not-show-client-hostname-information 212 | requestLog.clientIp = socket.localAddress; 213 | requestLog.clientPort = socket.localPort; 214 | 215 | logger.debug(`${logPre} Request on response. Socket chain: ${ 216 | socket.localAddress 217 | }:${socket.localPort} > ${ 218 | socket.remoteAddress 219 | }:${socket.remotePort}. Response status code: ${ 220 | response.statusCode 221 | }. Cost: ${ 222 | timestamps.onResponse - timestamps.onSocket 223 | } ms`); 224 | 225 | // responseInfo can't retrieve data until response "end" event 226 | const responseInfo = captureIncoming(response); 227 | 228 | response.once("end", () => { 229 | timestamps.responseClose = new Date().getTime(); 230 | 231 | requestLog.statusCode = response.statusCode; 232 | requestLog.responseLength = responseInfo.bodyLength; 233 | requestLog.responseType = response.headers["content-type"]; 234 | requestLog.responseHeader = ((): string => { 235 | const result = []; 236 | result.push(`HTTP/${response.httpVersion} ${ 237 | response.statusCode} ${response.statusMessage}`); 238 | 239 | const cloneHeaders = cloneDeep(response.headers); 240 | // Transfer a chunked response to a full response. 241 | // https://imququ.com/post/transfer-encoding-header-in-http.html 242 | if (!cloneHeaders["content-length"] 243 | && responseInfo.bodyLength >= 0) { 244 | delete cloneHeaders["transfer-encoding"]; 245 | cloneHeaders["content-length"] = String(responseInfo.bodyLength); 246 | } 247 | 248 | Object.keys(cloneHeaders).forEach((key) => { 249 | result.push(`${key}: ${cloneHeaders[key]}`); 250 | }); 251 | 252 | result.push(""); 253 | result.push(""); 254 | 255 | return result.join("\r\n"); 256 | })(); 257 | 258 | requestLog.responseBody = responseInfo.body.toString("base64"); 259 | 260 | logger.debug(`${logPre} Response on end. Body size:${ 261 | requestLog.responseLength 262 | }. Cost: ${ 263 | timestamps.responseClose - timestamps.onSocket 264 | } ms`); 265 | 266 | finishRequest(); 267 | }); 268 | }); 269 | 270 | return request; 271 | } 272 | ); 273 | 274 | let hacked = false; 275 | let originHttpRequest = null; 276 | let originHttpsRequest = null; 277 | export const requestHack = (): void => { 278 | if (!hacked) { 279 | originHttpRequest = http.request; 280 | originHttpsRequest = https.request; 281 | // eslint-disable-next-line 282 | // @ts-ignore 283 | // By default, ts not allow us to rewrite original methods. 284 | http.request = hack(http.request, "http:"); 285 | // eslint-disable-next-line 286 | // @ts-ignore 287 | // By default, ts not allow us to rewrite original methods. 288 | https.request = hack(https.request, "https:"); 289 | 290 | hacked = true; 291 | } 292 | }; 293 | 294 | export const requestRestore = (): void => { 295 | if (hacked) { 296 | // eslint-disable-next-line 297 | // @ts-ignore 298 | // By default, ts not allow us to rewrite original methods. 299 | http.request = originHttpRequest; 300 | // eslint-disable-next-line 301 | // @ts-ignore 302 | // By default, ts not allow us to rewrite original methods. 303 | https.request = originHttpsRequest; 304 | 305 | hacked = false; 306 | } 307 | }; 308 | -------------------------------------------------------------------------------- /lib/core/runtime/capture/outgoing.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as http from "http"; 10 | 11 | // Max request body size 12 | const maxBodySize = 512 * 1024; 13 | 14 | export const captureOutgoing = (outgoing: http.OutgoingMessage): void => { 15 | let bodyLength = 0; 16 | const body: Buffer[] = []; 17 | 18 | (outgoing as any)._send = ((fn) => ( 19 | data: Buffer | string, 20 | encodingOrCallback?: string | ((err?: Error) => void) | undefined, 21 | callbackOrUndefined?: ((err?: Error) => void) | undefined 22 | ): boolean => { 23 | let encoding: BufferEncoding = null; 24 | let callback: (err?: Error) => void; 25 | 26 | if (typeof encodingOrCallback === "function") { 27 | callback = encodingOrCallback; 28 | } else if (typeof callbackOrUndefined === "function") { 29 | encoding = encodingOrCallback as BufferEncoding; 30 | callback = callbackOrUndefined; 31 | } 32 | 33 | // 达到最大长度限制,不再收集包内容 34 | if (bodyLength > maxBodySize) { 35 | return fn.apply(outgoing, [data, encoding, callback]); 36 | } 37 | 38 | const buffer = ((): Buffer => { 39 | if (Buffer.isBuffer(data)) { 40 | return data; 41 | } 42 | 43 | return Buffer.from(data, encoding); 44 | })(); 45 | 46 | bodyLength += buffer.length; 47 | body.push(buffer); 48 | 49 | return fn.apply(outgoing, [data, encoding, callback]); 50 | })((outgoing as any)._send); 51 | 52 | (outgoing as any)._finish = ((fn) => ( 53 | ...args: unknown[] 54 | ): void => { 55 | (outgoing as any)._body = Buffer.concat(body); 56 | 57 | (outgoing as any)._bodyTooLarge = bodyLength > maxBodySize; 58 | (outgoing as any)._bodyLength = bodyLength; 59 | 60 | return fn.apply(outgoing, args); 61 | })((outgoing as any)._finish); 62 | }; 63 | -------------------------------------------------------------------------------- /lib/core/runtime/console.hack.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as util from "util"; 10 | 11 | import logger from "../logger/index"; 12 | import getCurrentContext from "../context"; 13 | 14 | let consoleHacked = false; 15 | 16 | export const consoleHack = (): void => { 17 | if (!consoleHacked) { 18 | consoleHacked = true; 19 | 20 | console.originDebug = console.debug; 21 | console.originLog = console.log; 22 | console.originInfo = console.info; 23 | console.originDir = console.dir; 24 | console.originWarn = console.warn; 25 | console.originError = console.error; 26 | 27 | console.debug = ( 28 | message?: any, 29 | ...optionalParams: any[] 30 | ): void => { 31 | if (getCurrentContext() === null) { 32 | return console.originDebug(message, ...optionalParams); 33 | } 34 | 35 | return logger.writeLog( 36 | "DEBUG", 37 | `${util.format(message, ...optionalParams)}` 38 | ); 39 | }; 40 | 41 | console.log = ( 42 | message?: any, 43 | ...optionalParams: any[] 44 | ): void => { 45 | if (getCurrentContext() === null) { 46 | return console.originLog(message, ...optionalParams); 47 | } 48 | 49 | return logger.writeLog( 50 | "DEBUG", 51 | `${util.format(message, ...optionalParams)}` 52 | ); 53 | }; 54 | 55 | console.info = ( 56 | message?: any, 57 | ...optionalParams: any[] 58 | ): void => { 59 | if (getCurrentContext() === null) { 60 | return console.originInfo(message, ...optionalParams); 61 | } 62 | 63 | return logger.writeLog( 64 | "INFO", 65 | `${util.format(message, ...optionalParams)}` 66 | ); 67 | }; 68 | 69 | console.dir = ( 70 | obj: any, 71 | options?: NodeJS.InspectOptions 72 | ): void => { 73 | if (getCurrentContext() === null) { 74 | return console.originDir(obj, options); 75 | } 76 | 77 | return logger.writeLog( 78 | "INFO", 79 | `${util.inspect(obj, { 80 | customInspect: false, 81 | ...options 82 | })}` 83 | ); 84 | }; 85 | 86 | console.warn = ( 87 | message?: any, 88 | ...optionalParams: any[] 89 | ): void => { 90 | if (getCurrentContext() === null) { 91 | return console.originWarn(message, ...optionalParams); 92 | } 93 | 94 | return logger.writeLog( 95 | "WARN", 96 | `${util.format(message, ...optionalParams)}` 97 | ); 98 | }; 99 | 100 | console.error = ( 101 | message?: any, 102 | ...optionalParams: any[] 103 | ): void => { 104 | if (getCurrentContext() === null) { 105 | return console.originError(message, ...optionalParams); 106 | } 107 | 108 | return logger.writeLog( 109 | "ERROR", 110 | `${util.format(message, ...optionalParams)}` 111 | ); 112 | }; 113 | 114 | // hack process._stdout 115 | (process.stdout as any).originWrite = process.stdout.write; 116 | process.stdout.write = ( 117 | data: Buffer | string, 118 | encodingOrCallback?: string | ((err?: Error) => void) | undefined 119 | ): boolean => { 120 | let encoding: BufferEncoding; 121 | if (typeof encodingOrCallback !== "function") { 122 | encoding = encodingOrCallback as BufferEncoding; 123 | } 124 | 125 | logger.writeLog( 126 | "DEBUG", 127 | data.toString(encoding).replace(/\n$/, "") // 去掉换行符 128 | ); 129 | 130 | return true; 131 | }; 132 | 133 | // hack process._stderr 134 | (process.stderr as any).originWrite = process.stderr.write; 135 | process.stderr.write = ( 136 | data: Buffer | string, 137 | encodingOrCallback?: string | ((err?: Error) => void) | undefined 138 | ): boolean => { 139 | let encoding: BufferEncoding; 140 | if (typeof encodingOrCallback !== "function") { 141 | encoding = encodingOrCallback as BufferEncoding; 142 | } 143 | 144 | logger.writeLog( 145 | "ERROR", 146 | data.toString(encoding).replace(/\n$/, "") // 去掉换行符 147 | ); 148 | 149 | return true; 150 | }; 151 | } 152 | }; 153 | 154 | export const consoleRestore = (): void => { 155 | if (consoleHacked) { 156 | consoleHacked = false; 157 | 158 | console.debug = console.originDebug; 159 | console.info = console.originInfo; 160 | console.log = console.originLog; 161 | console.warn = console.originWarn; 162 | console.error = console.originError; 163 | console.dir = console.originDir; 164 | 165 | process.stdout.write = (process.stdout as any).originWrite; 166 | process.stderr.write = (process.stderr as any).originWrite; 167 | 168 | delete console.originDebug; 169 | delete console.originInfo; 170 | delete console.originLog; 171 | delete console.originWarn; 172 | delete console.originError; 173 | delete console.originDir; 174 | 175 | delete (process.stdout as any).originWrite; 176 | delete (process.stderr as any).originWrite; 177 | } 178 | }; 179 | -------------------------------------------------------------------------------- /lib/core/runtime/create-server.hack.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as http from "http"; 10 | import * as https from "https"; 11 | import * as domain from "domain"; 12 | import { Context, RequestLog } from "../context"; 13 | import { address } from "ip"; 14 | import { AddressInfo, isIP } from "net"; 15 | import { captureOutgoing } from "./capture/outgoing"; 16 | import { captureIncoming } from "./capture/incoming"; 17 | import { eventBus, EVENT_LIST } from "../bus"; 18 | 19 | let httpCreateServerHacked = false; 20 | let httpsCreateServerHacked = false; 21 | 22 | let originHttpCreateServer = null; 23 | let originHttpsCreateServer = null; 24 | 25 | export const hack = ( 26 | originCreateServer: T 27 | ): ( 28 | ( 29 | optionsOrRequestListener: http.ServerOptions, 30 | requestListenerOrUndefined?: http.RequestListener 31 | ) => http.Server 32 | ) => ( 33 | ( 34 | optionsOrRequestListener: http.ServerOptions, 35 | requestListenerOrUndefined?: http.RequestListener 36 | ): http.Server => { 37 | let requestListener: http.RequestListener; 38 | let options: http.ServerOptions; 39 | if (typeof optionsOrRequestListener === "function") { 40 | requestListener = optionsOrRequestListener; 41 | } else { 42 | requestListener = requestListenerOrUndefined; 43 | options = optionsOrRequestListener; 44 | } 45 | 46 | const requestListenerWrap: http.RequestListener = (req, res) => { 47 | const start = new Date().getTime(); 48 | const timestamps: RequestLog["timestamps"] = { 49 | dnsTime: 0, 50 | requestStart: start, 51 | onSocket: start, 52 | onLookUp: start, 53 | requestFinish: start, 54 | socketConnect: start 55 | } as RequestLog["timestamps"]; 56 | 57 | // Creating a domain and wrapping the execution. 58 | const d = domain.create(); 59 | const context = new Context(); 60 | d.add(req); 61 | d.add(res); 62 | 63 | const clearDomain = (): void => { 64 | d.remove(req); 65 | d.remove(res); 66 | 67 | if (process.domain.currentContext) { 68 | process.domain.currentContext = null; 69 | } 70 | 71 | const parser = (req.socket as any).parser as any; 72 | if (parser && parser.domain) { 73 | (parser.domain as domain.Domain).exit(); 74 | parser.domain = null; 75 | } 76 | 77 | while (process.domain) { 78 | (process.domain as domain.Domain).exit(); 79 | } 80 | }; 81 | 82 | const requestInfo = captureIncoming(req); 83 | 84 | res.writeHead = ((fn): typeof res.writeHead => ( 85 | ...args: unknown[] 86 | ): ReturnType => { 87 | timestamps.onResponse = new Date().getTime(); 88 | 89 | eventBus.emit(EVENT_LIST.RESPONSE_START, { 90 | req, res, context 91 | }); 92 | 93 | captureOutgoing(res); 94 | 95 | return fn.apply(res, args); 96 | })(res.writeHead); 97 | 98 | res.once("finish", () => { 99 | context.currentRequest = { 100 | SN: context.SN, 101 | 102 | protocol: "HTTP", 103 | host: req.headers.host, 104 | path: req.url, 105 | 106 | process: `TSW: ${process.pid}`, 107 | 108 | clientIp: req.socket.remoteAddress, 109 | clientPort: req.socket.remotePort, 110 | serverIp: address(), 111 | serverPort: (req.socket.address() as AddressInfo).port, 112 | requestHeader: ((): string => { 113 | const result = []; 114 | result.push(`${req.method} ${ 115 | req.url} HTTP/${req.httpVersion}`); 116 | 117 | Object.keys(req.headers).forEach((key) => { 118 | result.push(`${key}: ${req.headers[key]}`); 119 | }); 120 | 121 | result.push(""); 122 | result.push(""); 123 | 124 | return result.join("\r\n"); 125 | })(), 126 | requestBody: requestInfo.body.toString("base64"), 127 | responseHeader: ((): string => { 128 | const result = []; 129 | result.push(`HTTP/${req.httpVersion} ${ 130 | res.statusCode} ${res.statusMessage}`); 131 | 132 | const resHeaders = res.getHeaders(); 133 | Object.keys(resHeaders).forEach((key) => { 134 | result.push(`${key}: ${resHeaders[key]}`); 135 | }); 136 | 137 | result.push(""); 138 | result.push(""); 139 | 140 | return result.join("\r\n"); 141 | })(), 142 | responseBody: (res as any)._body.toString("base64"), 143 | responseLength: (res as any)._bodyLength, 144 | responseType: res.getHeader("content-type"), 145 | statusCode: res.statusCode, 146 | timestamps 147 | } as RequestLog; 148 | 149 | clearDomain(); 150 | 151 | eventBus.emit(EVENT_LIST.RESPONSE_FINISH, { 152 | req, res, context 153 | }); 154 | }); 155 | 156 | res.once("close", () => { 157 | timestamps.responseClose = new Date().getTime(); 158 | clearDomain(); 159 | 160 | eventBus.emit(EVENT_LIST.RESPONSE_CLOSE, { 161 | req, res, context 162 | }); 163 | }); 164 | 165 | d.run(() => { 166 | process.domain.currentContext = context; 167 | 168 | eventBus.emit(EVENT_LIST.REQUEST_START, { 169 | req, context 170 | }); 171 | 172 | // proxy req to proxy env when hitting uid 173 | if ((isIP(context.proxyIp)) 174 | && !req.headers["x-tsw-proxy"]) { 175 | console.debug("isProxyUser..."); 176 | 177 | const requestOptions = { 178 | hostname: context.proxyIp, 179 | port: context.proxyPort, 180 | path: req.url, 181 | method: req.method, 182 | headers: { "x-tsw-proxy": "true", ...req.headers } 183 | }; 184 | console.debug("start proxy"); 185 | const proxyReq = http.request(requestOptions, (proxyRes) => { 186 | proxyRes.pipe(res); 187 | Object.keys(proxyRes.headers).forEach((headerType) => { 188 | res.setHeader(headerType, proxyRes.headers[headerType]); 189 | }); 190 | 191 | res.writeHead(proxyRes.statusCode); 192 | proxyRes.on("end", () => { 193 | console.debug("end proxy"); 194 | }); 195 | }); 196 | 197 | if (/POST|PUT/i.test(req.method)) { 198 | req.pipe(proxyReq); 199 | } else { 200 | proxyReq.end(); 201 | } 202 | 203 | proxyReq.on("error", (err) => { 204 | console.error("proxy fail..."); 205 | console.error(JSON.stringify(err)); 206 | if (res.headersSent) { 207 | res.end(); 208 | return; 209 | } 210 | 211 | res.setHeader("Content-Type", "text/html; charset=UTF-8"); 212 | res.writeHead(500); 213 | res.end(); 214 | }); 215 | 216 | return; 217 | } 218 | 219 | requestListener(req, res); 220 | }); 221 | }; 222 | 223 | if (options) { 224 | return originCreateServer.apply(this, [options, requestListenerWrap]); 225 | } 226 | 227 | return originCreateServer.apply(this, [requestListenerWrap]); 228 | } 229 | ); 230 | 231 | export const httpCreateServerHack = (): void => { 232 | if (!httpCreateServerHacked) { 233 | httpCreateServerHacked = true; 234 | originHttpCreateServer = http.createServer; 235 | 236 | // eslint-disable-next-line 237 | // @ts-ignore 238 | // By default, ts not allow us to rewrite original methods. 239 | http.createServer = hack(http.createServer); 240 | } 241 | }; 242 | 243 | export const httpsCreateServerHack = (): void => { 244 | if (!httpsCreateServerHacked) { 245 | httpsCreateServerHacked = true; 246 | originHttpsCreateServer = https.createServer; 247 | 248 | // eslint-disable-next-line 249 | // @ts-ignore 250 | // By default, ts not allow us to rewrite original methods. 251 | https.createServer = hack(https.createServer); 252 | } 253 | }; 254 | 255 | export const httpCreateServerRestore = (): void => { 256 | if (httpCreateServerHacked) { 257 | httpCreateServerHacked = false; 258 | // eslint-disable-next-line 259 | // @ts-ignore 260 | // By default, ts not allow us to rewrite original methods. 261 | http.createServer = originHttpCreateServer; 262 | } 263 | }; 264 | 265 | export const httpsCreateServerRestore = (): void => { 266 | if (httpsCreateServerHacked) { 267 | httpsCreateServerHacked = false; 268 | // eslint-disable-next-line 269 | // @ts-ignore 270 | // By default, ts not allow us to rewrite original methods. 271 | https.createServer = originHttpsCreateServer; 272 | } 273 | }; 274 | -------------------------------------------------------------------------------- /lib/core/runtime/dns.hack.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as dns from "dns"; 10 | import * as net from "net"; 11 | 12 | import { EVENT_LIST, eventBus } from "../bus"; 13 | import config from "../config"; 14 | import logger from "../logger"; 15 | 16 | type LookupCallback = ( 17 | err: NodeJS.ErrnoException | null, 18 | address: string | dns.LookupAddress[], 19 | family: number 20 | ) => void; 21 | 22 | type LookupSecondParam = 23 | | number 24 | | dns.LookupOneOptions 25 | | dns.LookupAllOptions 26 | | dns.LookupOptions 27 | | LookupCallback; 28 | 29 | let dnsHacked = false; 30 | let originDnsLookUp = null; 31 | 32 | export const dnsHack = (): void => { 33 | // Ensure hack can only be run once. 34 | if (!dnsHacked) { 35 | dnsHacked = true; 36 | originDnsLookUp = dns.lookup; 37 | 38 | // eslint-disable-next-line 39 | // @ts-ignore 40 | // By default, ts not allow us to rewrite original methods. 41 | dns.lookup = ( 42 | (lookup) => ( 43 | ( 44 | hostname: string, 45 | optionsOrCallback: LookupSecondParam, 46 | callbackOrUndefined?: LookupCallback 47 | ): void => { 48 | const start = Date.now(); 49 | 50 | const options = typeof optionsOrCallback === "function" 51 | ? undefined 52 | : optionsOrCallback; 53 | const callback = typeof optionsOrCallback === "function" 54 | ? optionsOrCallback 55 | : callbackOrUndefined; 56 | 57 | logger.debug(`dns lookup for ${hostname}`); 58 | 59 | // For http.request, if host is a ip 60 | // It will not entry dns.lookup by default 61 | // https://github.com/nodejs/node/blob/master/lib/net.js#L1002 62 | // But still need this, in case use call dns.lookup directly 63 | if (net.isIP(hostname)) { 64 | logger.debug(`dns lookup: ${hostname} is a ip`); 65 | if (options) { 66 | return lookup.apply(this, [hostname, options, callback]); 67 | } 68 | 69 | return lookup.apply(this, [hostname, callback]); 70 | } 71 | 72 | let isCalled = false; 73 | let timeoutError: Error; 74 | let timer: NodeJS.Timeout | undefined; 75 | 76 | const callbackWrap = ( 77 | err: NodeJS.ErrnoException, 78 | address: string | dns.LookupAddress[], 79 | family: number 80 | ): void => { 81 | if (isCalled) { 82 | return; 83 | } 84 | 85 | isCalled = true; 86 | 87 | const cost = Date.now() - start; 88 | if (!err) { 89 | logger.debug(`dns lookup [${cost}ms]: ${hostname} > ${address}`); 90 | eventBus.emit(EVENT_LIST.DNS_LOOKUP_SUCCESS, address); 91 | } else { 92 | logger.error(`dns lookup [${cost}ms]: ${hostname} > ${address}, 93 | error: ${err.stack}`); 94 | 95 | eventBus.emit(EVENT_LIST.DNS_LOOKUP_ERROR, err); 96 | } 97 | 98 | if (timer) clearTimeout(timer); 99 | if (callback) callback(err, address, family); 100 | }; 101 | 102 | timer = setTimeout(() => { 103 | timeoutError = new Error("Dns Lookup Timeout"); 104 | callbackWrap(timeoutError, "", 0); 105 | }, (config.timeout && config.timeout.dns) || 3000); 106 | 107 | if (options) { 108 | return lookup.apply(this, [hostname, options, callbackWrap]); 109 | } 110 | 111 | return lookup.apply(this, [hostname, callbackWrap]); 112 | } 113 | ) 114 | )(dns.lookup); 115 | } 116 | }; 117 | 118 | export const dnsRestore = (): void => { 119 | if (dnsHacked) { 120 | // eslint-disable-next-line 121 | // @ts-ignore 122 | // By default, ts not allow us to rewrite original methods. 123 | dns.lookup = originDnsLookUp; 124 | dnsHacked = false; 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /lib/core/util/__test__/isInspect.test.ts: -------------------------------------------------------------------------------- 1 | import isInspect from "../isInspect"; 2 | 3 | describe("test inspect env", () => { 4 | test("test isInspect when NODE_OPTIONS === ''", () => { 5 | process.env.NODE_OPTIONS = ""; 6 | expect(isInspect()).toBe(false); 7 | }); 8 | 9 | test("test isInspect when NODE_OPTIONS === '--require=ts-node/register'", 10 | () => { 11 | process.env.NODE_OPTIONS = "--require=ts-node/register"; 12 | expect(isInspect()).toBe(false); 13 | }); 14 | 15 | test("test isInspect when NODE_OPTIONS === '--inspect'", () => { 16 | process.env.NODE_OPTIONS = "--inspect"; 17 | expect(isInspect()).toBe(true); 18 | }); 19 | 20 | test("test isInspect when NODE_OPTIONS === '--inspect-brk=true'", () => { 21 | process.env.NODE_OPTIONS = "--inspect-brk=true"; 22 | expect(isInspect()).toBe(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/core/util/isInspect.ts: -------------------------------------------------------------------------------- 1 | const isInspect = (): boolean => { 2 | const nodeOptions = process.env.NODE_OPTIONS; 3 | return Boolean(nodeOptions && ( 4 | nodeOptions.includes("--inspect") 5 | || nodeOptions.includes("--inspect-brk"))); 6 | }; 7 | 8 | export default isInspect; 9 | -------------------------------------------------------------------------------- /lib/core/util/isLinux.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | const isOSX = process.platform === "darwin"; 10 | const isWindows = process.platform === "win32"; 11 | export default !(isWindows || isOSX); 12 | -------------------------------------------------------------------------------- /lib/core/winston.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | import * as winston from "winston"; 10 | import logger from "./logger/index"; 11 | 12 | export const winstonHack = (): void => { 13 | const { winstonTransports: transports } = global.tswConfig; 14 | if (Array.isArray(transports) && transports.length) { 15 | logger.winstonLogger = winston.createLogger({ 16 | format: winston.format.simple(), 17 | transports 18 | }); 19 | } 20 | }; 21 | 22 | export const winstonRestore = (): void => { 23 | if (logger.winstonLogger) { 24 | logger.winstonLogger = undefined; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { consoleHack, consoleRestore } from "./core/runtime/console.hack"; 3 | import { 4 | httpCreateServerHack, 5 | httpsCreateServerHack, 6 | httpCreateServerRestore, 7 | httpsCreateServerRestore 8 | } from "./core/runtime/create-server.hack"; 9 | import { dnsHack, dnsRestore } from "./core/runtime/dns.hack"; 10 | import { requestHack, requestRestore } from "./core/runtime/capture/index"; 11 | import { winstonHack, winstonRestore } from "./core/winston"; 12 | import { eventBus } from "./core/bus"; 13 | import logger from "./core/logger"; 14 | 15 | export const installHacks = (): void => { 16 | httpCreateServerHack(); 17 | httpsCreateServerHack(); 18 | dnsHack(); 19 | consoleHack(); 20 | requestHack(); 21 | winstonHack(); 22 | }; 23 | 24 | export const uninstallHacks = (): void => { 25 | httpCreateServerRestore(); 26 | httpsCreateServerRestore(); 27 | dnsRestore(); 28 | consoleRestore(); 29 | requestRestore(); 30 | winstonRestore(); 31 | }; 32 | 33 | export default async ( 34 | basePath: string, 35 | mainPath: string, 36 | configPath: string 37 | ): Promise => { 38 | const configAbsolutePath = path.resolve(basePath, configPath); 39 | global.tswConfig = await import(configAbsolutePath); 40 | 41 | logger.setCleanLog(global.tswConfig.cleanLog); 42 | logger.setLogLevel(global.tswConfig.logLevel); 43 | 44 | if (global.tswConfig.plugins) { 45 | // eslint-disable-next-line no-restricted-syntax 46 | for (const plugin of global.tswConfig.plugins) { 47 | try { 48 | // eslint-disable-next-line no-await-in-loop 49 | await plugin.init(eventBus, global.tswConfig); 50 | } catch (e) { 51 | console.error(`${plugin.name} 插件初始化失败: ${e.message}`); 52 | process.exit(-1); 53 | } 54 | } 55 | } 56 | 57 | installHacks(); 58 | await import(path.resolve(basePath, mainPath)); 59 | }; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tswjs/tsw", 3 | "version": "2.0.0-alpha.10", 4 | "description": "A Node.js infrastructure which is designed for improving the efficiency of locating problems, providing multiple functions for front-end developers", 5 | "main": "dist/index.js", 6 | "bin": "dist/cli.js", 7 | "scripts": { 8 | "commitlint": "commitlint --color", 9 | "test": "jest --coverage", 10 | "build": "tsc", 11 | "dev": "tsc --watch --sourceMap", 12 | "lint": "eslint --ext .ts .", 13 | "lint:fix": "eslint --fix --ext .ts .", 14 | "postinstall": "patch-package", 15 | "prepublishOnly": "rm -rf patches", 16 | "benchmark": "cd benchmark && make" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/Tencent/TSW.git" 21 | }, 22 | "keywords": [ 23 | "tsw" 24 | ], 25 | "engines": { 26 | "node": ">= 12.0.0" 27 | }, 28 | "license": "MIT", 29 | "devDependencies": { 30 | "@commitlint/cli": "^9.1.0", 31 | "@commitlint/config-conventional": "^7.5.0", 32 | "@types/ip": "^1.1.0", 33 | "@types/jest": "^24.9.1", 34 | "@types/lodash": "^4.14.149", 35 | "@types/node": "^11.9.5", 36 | "@types/yargs": "^13.0.3", 37 | "@typescript-eslint/eslint-plugin": "^2.7.0", 38 | "@typescript-eslint/parser": "^2.7.0", 39 | "eslint": "^6.6.0", 40 | "eslint-config-airbnb": "^18.0.1", 41 | "eslint-import-resolver-typescript": "^2.0.0", 42 | "eslint-plugin-eslint-comments": "^3.1.2", 43 | "eslint-plugin-import": "^2.18.2", 44 | "eslint-plugin-jsdoc": "^18.0.1", 45 | "husky": "^1.3.1", 46 | "jest": "^26.0.0", 47 | "lint-staged": "^8.1.4", 48 | "ts-jest": "^26.1.0", 49 | "ts-node": "^8.0.2", 50 | "typescript": "^3.9.5" 51 | }, 52 | "husky": { 53 | "hooks": { 54 | "commit-msg": "commitlint -e .git/COMMIT_EDITMSG", 55 | "pre-commit": "lint-staged" 56 | } 57 | }, 58 | "lint-staged": { 59 | "*.ts": [ 60 | "yarn lint:fix", 61 | "git add" 62 | ] 63 | }, 64 | "dependencies": { 65 | "chalk": "^3.0.0", 66 | "ip": "^1.1.5", 67 | "lodash": "^4.17.15", 68 | "moment": "^2.24.0", 69 | "patch-package": "^6.2.2", 70 | "postinstall-postinstall": "^2.1.0", 71 | "winston": "^3.2.1", 72 | "winston-transport": "^4.3.0", 73 | "yargs": "^15.3.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /patches/jest-util+26.6.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/jest-util/build/createProcessObject.js b/node_modules/jest-util/build/createProcessObject.js 2 | index 90de355..c317789 100644 3 | --- a/node_modules/jest-util/build/createProcessObject.js 4 | +++ b/node_modules/jest-util/build/createProcessObject.js 5 | @@ -117,18 +117,11 @@ function _default() { 6 | 7 | newProcess.send = () => {}; 8 | 9 | - const domainPropertyDescriptor = Object.getOwnPropertyDescriptor( 10 | - newProcess, 11 | - 'domain' 12 | - ); 13 | - 14 | - if (domainPropertyDescriptor && !domainPropertyDescriptor.enumerable) { 15 | - Object.defineProperty(newProcess, 'domain', { 16 | - get() { 17 | - return process.domain; 18 | - } 19 | - }); 20 | - } 21 | + Object.defineProperty(newProcess, 'domain', { 22 | + get() { 23 | + return process.domain; 24 | + } 25 | + }); 26 | 27 | return newProcess; 28 | } 29 | -------------------------------------------------------------------------------- /static/images/appid-appkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/appid-appkey.png -------------------------------------------------------------------------------- /static/images/capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/capture.png -------------------------------------------------------------------------------- /static/images/create-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/create-app.png -------------------------------------------------------------------------------- /static/images/log-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/log-view.png -------------------------------------------------------------------------------- /static/images/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/log.png -------------------------------------------------------------------------------- /static/images/user/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/01.png -------------------------------------------------------------------------------- /static/images/user/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/02.png -------------------------------------------------------------------------------- /static/images/user/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/03.png -------------------------------------------------------------------------------- /static/images/user/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/04.png -------------------------------------------------------------------------------- /static/images/user/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/05.png -------------------------------------------------------------------------------- /static/images/user/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/06.png -------------------------------------------------------------------------------- /static/images/user/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/07.png -------------------------------------------------------------------------------- /static/images/user/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/08.png -------------------------------------------------------------------------------- /static/images/user/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/09.png -------------------------------------------------------------------------------- /static/images/user/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/10.png -------------------------------------------------------------------------------- /static/images/user/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/11.png -------------------------------------------------------------------------------- /static/images/user/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/12.png -------------------------------------------------------------------------------- /static/images/user/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/13.png -------------------------------------------------------------------------------- /static/images/user/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/14.png -------------------------------------------------------------------------------- /static/images/user/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/15.png -------------------------------------------------------------------------------- /static/images/user/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/16.png -------------------------------------------------------------------------------- /static/images/user/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/17.png -------------------------------------------------------------------------------- /static/images/user/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/18.png -------------------------------------------------------------------------------- /static/images/user/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/19.png -------------------------------------------------------------------------------- /static/images/user/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/20.png -------------------------------------------------------------------------------- /static/images/user/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/21.png -------------------------------------------------------------------------------- /static/images/user/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/22.png -------------------------------------------------------------------------------- /static/images/user/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/23.png -------------------------------------------------------------------------------- /static/images/user/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/24.png -------------------------------------------------------------------------------- /static/images/user/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/25.png -------------------------------------------------------------------------------- /static/images/user/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/26.png -------------------------------------------------------------------------------- /static/images/user/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/user/27.png -------------------------------------------------------------------------------- /static/images/winston-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tencent/TSW/c241c71187a0aba69140ae0782254fc9bad40760/static/images/winston-log.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "target": "es2018", 5 | "module": "commonjs", 6 | // "strict": true, 7 | "lib": [ 8 | "es2017", 9 | ], 10 | "resolveJsonModule": true, 11 | // "noImplicitAny": true 12 | "sourceMap": true, // for debug 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typings/type.d.ts: -------------------------------------------------------------------------------- 1 | /* ! 2 | * Tencent is pleased to support the open source community by making Tencent Server Web available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 | * http://opensource.org/licenses/MIT 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | declare interface Console { 10 | originDebug(message?: any, ...optionalParams: any[]): void; 11 | originLog(message?: any, ...optionalParams: any[]): void; 12 | originInfo(message?: any, ...optionalParams: any[]): void; 13 | originDir(message?: any, ...optionalParams: any[]): void; 14 | originWarn(message?: any, ...optionalParams: any[]): void; 15 | originError(message?: any, ...optionalParams: any[]): void; 16 | } 17 | 18 | declare namespace NodeJS { 19 | interface Process { 20 | SN: number; 21 | } 22 | 23 | interface Domain { 24 | currentContext?: any; 25 | } 26 | interface Global { 27 | tswConfig: { 28 | plugins: any[]; 29 | winstonTransports?: any[]; 30 | cleanLog?: boolean; 31 | logLevel?: "DEBUG" | "INFO" | "WARN" | "ERROR"; 32 | }; 33 | } 34 | } 35 | --------------------------------------------------------------------------------