├── index.js ├── client.js ├── package.json ├── server.js ├── swaggerUrlModel.js ├── customizeSwagger ├── customizeSwagger.scss └── customizeSwagger.js ├── README.md ├── importController.js ├── run.js └── LICENSE /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | import customizeSwagger from './customizeSwagger/customizeSwagger.js' 2 | 3 | function hander(routers) { 4 | routers.swagger = { 5 | name: 'swagger接口自定义导入', 6 | component: customizeSwagger 7 | }; 8 | } 9 | 10 | module.exports = function() { 11 | this.bindHook('sub_setting_nav', hander); 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "yapi-plugin", 3 | "name": "yapi-plugin-import-swagger-customize", 4 | "version": "2.0.4", 5 | "description": "yapi自定义导入插件", 6 | "main": "index.js", 7 | "keywords": [ 8 | "yapi", 9 | "swagger", 10 | "plugin", 11 | "import" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/follow-my-heart/yapi-plugin-import-swagger-customize.git" 16 | }, 17 | "scripts": { 18 | "dev": "node index.js" 19 | }, 20 | "license": "Apache-2.0" 21 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const controller = require('./importController.js'); 2 | 3 | module.exports = function () { 4 | this.bindHook('add_router', function (addRouter) { 5 | addRouter({ 6 | controller: controller, 7 | method: 'post', 8 | path: 'customizeSwagger/getSwaggerUrl', 9 | action: 'getSwaggerUrl' 10 | }); 11 | addRouter({ 12 | controller: controller, 13 | method: 'post', 14 | path: 'customizeSwagger/saveSwaggerUrl', 15 | action: 'saveSwaggerUrl' 16 | }); 17 | addRouter({ 18 | controller: controller, 19 | method: 'post', 20 | path: 'customizeSwagger/updateData', 21 | action: 'updateData' 22 | }); 23 | }); 24 | }; -------------------------------------------------------------------------------- /swaggerUrlModel.js: -------------------------------------------------------------------------------- 1 | const baseModel = require('models/base.js'); 2 | 3 | class swaggerUrlModel extends baseModel { 4 | getName() { 5 | return 'import-swagger-url-model'; 6 | } 7 | 8 | getSchema() { 9 | return { 10 | projectId: { type: Number, required: true }, 11 | swaggerUrl: { type: String, required: true } 12 | }; 13 | } 14 | 15 | getByProjectId(projectId) { 16 | return this.model.findOne({ 17 | projectId: projectId 18 | }); 19 | } 20 | 21 | save(data) { 22 | let m = new this.model(data); 23 | return m.save(); 24 | } 25 | 26 | up(data) { 27 | let id = data._id; 28 | delete data._id; 29 | return this.model.update({ 30 | _id: id 31 | }, data) 32 | } 33 | } 34 | 35 | module.exports = swaggerUrlModel; 36 | -------------------------------------------------------------------------------- /customizeSwagger/customizeSwagger.scss: -------------------------------------------------------------------------------- 1 | .appiont-swagger-data{ 2 | width: 100%; 3 | height: 100%; 4 | padding: 20px; 5 | background-color: #ffffff; 6 | 7 | .card{ 8 | width: 800px; 9 | margin: 0 auto; 10 | background-color: #ececec; 11 | padding: 16px; 12 | border-radius: 4px; 13 | 14 | .import-label{ 15 | margin: 16px 0; 16 | font-weight: 500; 17 | } 18 | 19 | .swagger-url{ 20 | display: flex; 21 | height: 32px; 22 | line-height: 32px; 23 | 24 | .url-button{ 25 | margin-left: 20px; 26 | } 27 | } 28 | 29 | .import-select{ 30 | display: block; 31 | } 32 | 33 | .input-radio{ 34 | height: 35px; 35 | } 36 | 37 | .import-button{ 38 | margin-top: 16px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yapi-plugin-import-swagger-customize 2 | yapi自定义导入swagger数据 3 | 4 | ### 特性 5 | 6 | * 新增、更新指定名称接口 7 | * 新增、更新多个接口用,隔开 8 | * 更新接口支持下拉菜单、多选和搜索 9 | * 支持保存swaggerUrl 10 | 11 | ### 安装插件 12 | 13 | > 安装ykit(已经装过的请忽略) 14 | 15 | ```shell 16 | npm install -g ykit 17 | ``` 18 | 19 | > 安装yapi(已经装过的请忽略) 20 | 21 | ```shell 22 | npm install -g yapi-cli --registry https://registry.npm.taobao.org 23 | ``` 24 | > 安装插件 25 | 26 | ```shell 27 | yapi plugin --name yapi-plugin-import-swagger-customize 28 | ``` 29 | 30 | ### 在config.json中新增插件配置 31 | 32 | 使用yapi命令下载插件时,本配置会自动添加 33 | 34 | ```json 35 | "plugins": [ 36 | { 37 | "name": "import-swagger-customize" 38 | } 39 | ] 40 | ``` 41 | ### 新增接口 42 | ![add](https://user-images.githubusercontent.com/20868829/61109083-9ad27e00-a4b6-11e9-9805-5e6a977bc0ff.png) 43 | 44 | ### 更新接口 45 | ![update](https://user-images.githubusercontent.com/20868829/61109045-7e364600-a4b6-11e9-9a89-dfe52e6716be.png) 46 | -------------------------------------------------------------------------------- /importController.js: -------------------------------------------------------------------------------- 1 | const baseController = require('controllers/base.js'); 2 | const yapi = require('yapi.js'); 3 | const projectModel = require('models/project.js'); 4 | const tokenModel = require('models/token.js'); 5 | const swaggerUrlModel = require('./swaggerUrlModel'); 6 | const HanldeImportData = require('../../common/HandleImportData'); 7 | const sha = require('sha.js'); 8 | const { getToken } = require('utils/token'); 9 | const formatData = require('./run'); 10 | const axios = require('axios'); 11 | 12 | class importController extends baseController { 13 | constructor(ctx) { 14 | super(ctx); 15 | this.projectModel = yapi.getInst(projectModel); 16 | this.tokenModel = yapi.getInst(tokenModel); 17 | this.swaggerUrlModel = yapi.getInst(swaggerUrlModel); 18 | } 19 | 20 | // 查询swaggerURL 21 | async getSwaggerUrl (ctx) { 22 | const requestBody = ctx.request.body; 23 | const { projectId } = requestBody; 24 | if (!projectId) { 25 | return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); 26 | } 27 | const result = await this.swaggerUrlModel.getByProjectId(projectId); 28 | return (ctx.body = yapi.commons.resReturn(result, 0)); 29 | } 30 | 31 | // 保存swaggerUrl 32 | async saveSwaggerUrl (ctx) { 33 | const requestBody = ctx.request.body; 34 | const { projectId } = requestBody; 35 | if (!projectId) { 36 | return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); 37 | } 38 | let result; 39 | if (requestBody._id) { 40 | result = await this.swaggerUrlModel.up(requestBody); 41 | } else { 42 | result = await this.swaggerUrlModel.save(requestBody); 43 | } 44 | return (ctx.body = yapi.commons.resReturn(result, 0)); 45 | } 46 | 47 | // 新增、修改接口 48 | async updateData (ctx) { 49 | 50 | let successMessage; 51 | let errorMessage = []; 52 | const requestBody = ctx.request.body; 53 | const { importType, projectId, cat, swaggerUrl, interfaceName } = requestBody; 54 | if (!projectId) { 55 | return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); 56 | } 57 | // 获取项目信息 58 | const projectData = await this.projectModel.get(projectId); 59 | const { basepath, uid } = projectData; 60 | // 获取swaggerJSON 61 | const swaggerData = await this.getSwaggerData(swaggerUrl, ctx); 62 | 63 | if (swaggerData.errorMsg) { 64 | return (ctx.body = yapi.commons.resReturn(null, 404, swaggerData.errorMsg)); 65 | } 66 | 67 | // 格式化swagger数据 68 | const res = await formatData( 69 | swaggerData, 70 | interfaceName, 71 | err => { 72 | errorMessage.push(err); 73 | } 74 | ); 75 | await HanldeImportData( 76 | res, 77 | projectId, 78 | '', 79 | cat, 80 | basepath, 81 | importType === 'add' ? 'normal' : 'merge', 82 | err => { 83 | errorMessage.push(err); 84 | }, 85 | msg => { 86 | successMessage = msg; 87 | }, 88 | () => {}, 89 | await this.getProjectToken(projectId, uid), 90 | yapi.WEBCONFIG.port 91 | ); 92 | 93 | if (errorMessage.length > 0) { 94 | return (ctx.body = yapi.commons.resReturn(null, 404, errorMessage.join('\n'))); 95 | } 96 | ctx.body = yapi.commons.resReturn(null, 0, successMessage); 97 | 98 | } 99 | // 获取项目token 100 | async getProjectToken(project_id, uid) { 101 | try { 102 | let data = await this.tokenModel.get(project_id); 103 | let token; 104 | if (!data) { 105 | let passsalt = yapi.commons.randStr(); 106 | token = sha('sha1') 107 | .update(passsalt) 108 | .digest('hex') 109 | .substr(0, 20); 110 | 111 | await this.tokenModel.save({ project_id, token }); 112 | } else { 113 | token = data.token; 114 | } 115 | 116 | token = getToken(token, uid); 117 | 118 | return token; 119 | } catch (err) { 120 | return ""; 121 | } 122 | } 123 | // 获取swaggerJSON 124 | async getSwaggerData(swaggerUrl) { 125 | try { 126 | const response = await axios.get(swaggerUrl); 127 | return response.data; 128 | } catch (e) { 129 | return { 130 | errorMsg: '获取数据失败,请确认 swaggerUrl 是否正确' 131 | } 132 | } 133 | } 134 | } 135 | 136 | module.exports = importController; -------------------------------------------------------------------------------- /customizeSwagger/customizeSwagger.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { Button, Radio, Select, Input, message, Tooltip, Icon } from 'antd'; 5 | import axios from 'axios'; 6 | import './customizeSwagger.scss'; 7 | 8 | const RadioGroup = Radio.Group; 9 | const Option = Select.Option; 10 | 11 | @connect( 12 | state => { 13 | return { 14 | projectMsg: state.project.currProject 15 | }; 16 | }, 17 | {} 18 | ) 19 | export default class ProjectInterfaceSync extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | // 项目所有接口list 24 | interfaceList: [], 25 | // 导入接口的方式 26 | importType: 'add', 27 | // 项目的swagger json地址 28 | swaggerUrl: '', 29 | // 是否已有url 30 | hasUrl: false, 31 | // 新增或更新接口arr 32 | interfaceName: [], 33 | // 更新接口接口名输入形式 34 | inputType: 'select', 35 | _id: null 36 | }; 37 | } 38 | 39 | static propTypes = { 40 | projectId: PropTypes.number, 41 | handleSwaggerUrlData: PropTypes.func, 42 | projectMsg: PropTypes.object, 43 | swaggerUrlData: PropTypes.string 44 | }; 45 | 46 | componentDidMount() { 47 | this.getSwaggerUrl(); 48 | } 49 | // 获取是否存在swaggerUrl 50 | getSwaggerUrl = () => { 51 | const params = { 52 | projectId: this.props.projectId 53 | } 54 | axios.post('/api/plugin/customizeSwagger/getSwaggerUrl', params).then(res => { 55 | const { errcode, data, errmsg } = res.data 56 | if (errcode === 0) { 57 | if (data) { 58 | const { swaggerUrl, _id } = data 59 | this.setState({ 60 | swaggerUrl, 61 | hasUrl: true, 62 | _id 63 | }) 64 | } 65 | } else { 66 | message.error(errmsg); 67 | } 68 | }); 69 | } 70 | // 保存swaggerUrl 71 | saveSwaggerUrl = () => { 72 | const { swaggerUrl, _id } = this.state 73 | const params = { 74 | projectId: this.props.projectId, 75 | swaggerUrl, 76 | _id 77 | } 78 | axios.post('/api/plugin/customizeSwagger/saveSwaggerUrl', params).then(res => { 79 | const { errcode, errmsg } = res.data 80 | if (errcode === 0) { 81 | message.success(errmsg); 82 | this.setState({ 83 | hasUrl: true 84 | }) 85 | } else { 86 | message.error(errmsg); 87 | } 88 | }); 89 | } 90 | // 获取项目所有接口list 91 | getInterfaceList = () => { 92 | const { projectMsg = {} } = this.props 93 | const { basepath = '' } = projectMsg 94 | axios.get(`/api/interface/list_menu?project_id=${this.props.projectId}`).then(res => { 95 | const { errcode, data, errmsg } = res.data 96 | if (errcode === 0) { 97 | let interfaceList = []; 98 | data.forEach(controller => { 99 | controller.list.forEach(item => { 100 | let path = item.path 101 | // 处理baseUrl 102 | if (basepath) { 103 | path = item.path.indexOf(basepath) === 0 ? item.path.substr(basepath.length) : path; 104 | path = `${basepath}${path}` 105 | } 106 | interfaceList.push(path) 107 | }) 108 | }); 109 | this.setState({ 110 | interfaceList 111 | }); 112 | } else { 113 | message.error(errmsg); 114 | } 115 | }); 116 | } 117 | 118 | changeSwaggerUrl = () => { 119 | this.setState({ 120 | hasUrl: false, 121 | swaggerUrl: '' 122 | }); 123 | } 124 | importTypeChange = e => { 125 | const importType = e.target.value; 126 | this.setState({ 127 | importType 128 | }); 129 | importType === 'update' && this.state.interfaceList.length === 0 && this.getInterfaceList(); 130 | }; 131 | 132 | inputTypeChange = e => { 133 | this.setState({ 134 | inputType: e.target.value 135 | }); 136 | }; 137 | 138 | swaggerUrlInput = e => { 139 | this.setState({ 140 | swaggerUrl: String(e.target.value).trim() 141 | }); 142 | }; 143 | 144 | interfaceNameInput = e => { 145 | const arr = e.target.value.split(',') 146 | this.setState({ 147 | interfaceName: arr.map(item => item.trim()) 148 | }); 149 | }; 150 | 151 | interfaceSelect = value => { 152 | this.setState({ 153 | interfaceName: value 154 | }); 155 | }; 156 | 157 | importData = async () => { 158 | const { importType, swaggerUrl, interfaceName } = this.state 159 | const { projectId, projectMsg = {} } = this.props 160 | 161 | if (!swaggerUrl) { 162 | return message.error('swagger url 不能为空!'); 163 | } 164 | const params = { 165 | importType, 166 | projectId, 167 | cat: projectMsg.cat, 168 | interfaceName, 169 | swaggerUrl 170 | } 171 | await axios.post('/api/plugin/customizeSwagger/updateData', params).then(res => { 172 | const { errcode, errmsg } = res.data 173 | if (errcode === 0) { 174 | message.success(errmsg); 175 | } else { 176 | message.error(errmsg); 177 | } 178 | }); 179 | }; 180 | 181 | render() { 182 | const { importType, inputType, interfaceList, swaggerUrl, hasUrl } = this.state 183 | return ( 184 |
185 |
186 | 187 | 添加接口 188 | 更新接口 189 | 190 |
项目的swaggerUrl:
191 | {hasUrl ? ( 192 |
193 | {swaggerUrl} 194 | 195 |
196 | ) : ( 197 |
198 | 202 | 203 |
204 | )} 205 |
接口名称  206 | 209 |

1、添加多个接口时请用,隔开

210 |

2、接口名称必须和后端定义的一致

211 |

3、项目接口过多时,更新接口请选择直接输入接口名称

212 |
213 | } 214 | > 215 | 216 | {' '}:
217 | {importType === 'add' ? ( 218 | 222 | ) : ( 223 |
224 |
225 | 226 | 选择接口名称 227 | 输入接口名称 228 | 229 |
230 | {inputType === 'select' ? ( 231 | 241 | ) : ( 242 | 246 | )} 247 |
248 | )} 249 | 250 |
251 | 252 | ); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore') 2 | const swagger = require('swagger-client'); 3 | const compareVersions = require('compare-versions'); 4 | 5 | var SwaggerData, isOAS3; 6 | function handlePath(path) { 7 | if (path === '/') return path; 8 | if (path.charAt(0) != '/') { 9 | path = '/' + path; 10 | } 11 | if (path.charAt(path.length - 1) === '/') { 12 | path = path.substr(0, path.length - 1); 13 | } 14 | return path; 15 | } 16 | 17 | function openapi2swagger(data) { 18 | data.swagger = '2.0'; 19 | _.each(data.paths, apis => { 20 | _.each(apis, api => { 21 | _.each(api.responses, res => { 22 | if ( 23 | res.content && 24 | res.content['application/json'] && 25 | typeof res.content['application/json'] === 'object' 26 | ) { 27 | Object.assign(res, res.content['application/json']); 28 | delete res.content; 29 | } 30 | }); 31 | if (api.requestBody) { 32 | if (!api.parameters) api.parameters = []; 33 | let body = { 34 | type: 'object', 35 | name: 'body', 36 | in: 'body' 37 | }; 38 | try { 39 | body.schema = api.requestBody.content['application/json'].schema; 40 | } catch (e) { 41 | body.schema = {}; 42 | } 43 | 44 | api.parameters.push(body); 45 | } 46 | }); 47 | }); 48 | 49 | return data; 50 | } 51 | 52 | async function handleSwaggerData(res) { 53 | 54 | return await new Promise(resolve => { 55 | let data = swagger({ 56 | spec: res 57 | }); 58 | 59 | data.then(res => { 60 | resolve(res.spec); 61 | }); 62 | }); 63 | } 64 | 65 | async function run(res, interfaceName, messageError) { 66 | let interfaceData = { apis: [], cats: [] }; 67 | if(typeof res === 'string' && res){ 68 | try{ 69 | res = JSON.parse(res); 70 | } catch (e) { 71 | console.error('json 解析出错', e.message); 72 | messageError('json 解析出错'); 73 | } 74 | } 75 | 76 | isOAS3 = res.openapi && compareVersions(res.openapi,'3.0.0') >= 0; 77 | if (isOAS3) { 78 | res = openapi2swagger(res); 79 | } 80 | res = await handleSwaggerData(res); 81 | SwaggerData = res; 82 | 83 | if (res.tags && Array.isArray(res.tags)) { 84 | res.tags.forEach(tag => { 85 | interfaceData.cats.push({ 86 | name: tag.name, 87 | desc: tag.description 88 | }); 89 | }); 90 | } 91 | let paths = {}; 92 | _.each(interfaceName, api => { 93 | const item = res.paths[api]; 94 | if (item){ 95 | paths[api] = res.paths[api]; 96 | } else { 97 | messageError(`接口${api}名称不正确`); 98 | } 99 | }) 100 | 101 | _.each(paths, (apis, path) => { 102 | // parameters is common parameters, not a method 103 | delete apis.parameters; 104 | 105 | _.each(apis, (api, method) => { 106 | 107 | api.path = path; 108 | api.method = method; 109 | let data = null; 110 | try { 111 | data = handleSwagger(api); 112 | if (data.catname) { 113 | if (!_.find(interfaceData.cats, item => item.name === data.catname)) { 114 | interfaceData.cats.push({ 115 | name: data.catname, 116 | desc: data.catname 117 | }); 118 | } 119 | } 120 | } catch (err) { 121 | data = null; 122 | } 123 | if (data) { 124 | interfaceData.apis.push(data); 125 | } 126 | }); 127 | }); 128 | return interfaceData; 129 | } 130 | 131 | function handleSwagger(data) { 132 | 133 | let api = {}; 134 | //处理基本信息 135 | api.method = data.method.toUpperCase(); 136 | api.title = data.summary || data.path; 137 | api.desc = data.description; 138 | api.catname = data.tags && Array.isArray(data.tags) ? data.tags[0] : null; 139 | 140 | api.path = handlePath(data.path); 141 | api.req_params = []; 142 | api.req_body_form = []; 143 | api.req_headers = []; 144 | api.req_query = []; 145 | api.req_body_type = 'raw'; 146 | api.res_body_type = 'raw'; 147 | 148 | if (data.produces && data.produces.indexOf('application/json') > -1) { 149 | api.res_body_type = 'json'; 150 | api.res_body_is_json_schema = true; 151 | } 152 | 153 | if (data.consumes && Array.isArray(data.consumes)) { 154 | if ( 155 | data.consumes.indexOf('application/x-www-form-urlencoded') > -1 || 156 | data.consumes.indexOf('multipart/form-data') > -1 157 | ) { 158 | api.req_body_type = 'form'; 159 | } else if (data.consumes.indexOf('application/json') > -1) { 160 | api.req_body_type = 'json'; 161 | api.req_body_is_json_schema = true; 162 | } 163 | } 164 | 165 | //处理response 166 | api.res_body = handleResponse(data.responses); 167 | try { 168 | JSON.parse(api.res_body); 169 | api.res_body_type = 'json'; 170 | api.res_body_is_json_schema = true; 171 | } catch (e) { 172 | api.res_body_type = 'raw'; 173 | } 174 | //处理参数 175 | function simpleJsonPathParse(key, json) { 176 | if (!key || typeof key !== 'string' || key.indexOf('#/') !== 0 || key.length <= 2) { 177 | return null; 178 | } 179 | let keys = key.substr(2).split('/'); 180 | keys = keys.filter(item => { 181 | return item; 182 | }); 183 | for (let i = 0, l = keys.length; i < l; i++) { 184 | try { 185 | json = json[keys[i]]; 186 | } catch (e) { 187 | json = ''; 188 | break; 189 | } 190 | } 191 | return json; 192 | } 193 | 194 | if (data.parameters && Array.isArray(data.parameters)) { 195 | data.parameters.forEach(param => { 196 | if (param && typeof param === 'object' && param.$ref) { 197 | param = simpleJsonPathParse(param.$ref, { parameters: SwaggerData.parameters }); 198 | } 199 | let defaultParam = { 200 | name: param.name, 201 | desc: param.description, 202 | required: param.required ? '1' : '0' 203 | }; 204 | 205 | switch (param.in) { 206 | case 'path': 207 | api.req_params.push(defaultParam); 208 | break; 209 | case 'query': 210 | api.req_query.push(defaultParam); 211 | break; 212 | case 'body': 213 | handleBodyPamras(param.schema, api); 214 | break; 215 | case 'formData': 216 | defaultParam.type = param.type === 'file' ? 'file' : 'text'; 217 | api.req_body_form.push(defaultParam); 218 | break; 219 | case 'header': 220 | api.req_headers.push(defaultParam); 221 | break; 222 | } 223 | }); 224 | } 225 | 226 | return api; 227 | } 228 | 229 | function isJson(json) { 230 | try { 231 | return JSON.parse(json); 232 | } catch (e) { 233 | return false; 234 | } 235 | } 236 | 237 | function handleBodyPamras(data, api) { 238 | api.req_body_other = JSON.stringify(data, null, 2); 239 | if (isJson(api.req_body_other)) { 240 | api.req_body_type = 'json'; 241 | api.req_body_is_json_schema = true; 242 | } 243 | } 244 | 245 | function handleResponse(api) { 246 | let res_body = ''; 247 | if (!api || typeof api !== 'object') { 248 | return res_body; 249 | } 250 | let codes = Object.keys(api); 251 | let curCode; 252 | if (codes.length > 0) { 253 | if (codes.indexOf('200') > -1) { 254 | curCode = '200'; 255 | } else curCode = codes[0]; 256 | 257 | let res = api[curCode]; 258 | if (res && typeof res === 'object') { 259 | if (res.schema) { 260 | res_body = JSON.stringify(res.schema, null, 2); 261 | } else if (res.description) { 262 | res_body = res.description; 263 | } 264 | } else if (typeof res === 'string') { 265 | res_body = res; 266 | } else { 267 | res_body = ''; 268 | } 269 | } else { 270 | res_body = ''; 271 | } 272 | return res_body; 273 | } 274 | 275 | 276 | 277 | 278 | module.exports = run; 279 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------