重新初始化

This commit is contained in:
LittleBoy 2022-05-11 16:20:23 +08:00
commit 22429c0735
22 changed files with 1163 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules/

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/deploy_server.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/deploy_server.iml" filepath="$PROJECT_DIR$/.idea/deploy_server.iml" />
</modules>
</component>
</project>

11
.idea/runConfigurations/bin_www.xml generated Normal file
View File

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="bin/www" type="NodeJSConfigurationType" path-to-js-file="bin/www" working-dir="$PROJECT_DIR$">
<envs>
<env name="DEBUG" value="deploy-server:*" />
</envs>
<EXTENSION ID="com.jetbrains.nodejs.run.NodeJSStartBrowserRunConfigurationExtension">
<browser url="http://localhost:3000/" />
</EXTENSION>
<method v="2" />
</configuration>
</component>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

24
app.js Normal file
View File

@ -0,0 +1,24 @@
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const bodyParser = require('body-parser');
const indexRouter = require('./routes/index');
const projectRouter = require('./routes/project');
const app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(bodyParser.urlencoded({
extended:true
}));
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/project', projectRouter);
module.exports = app;

92
bin/www Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('deploy-server:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port,()=>{
console.log( `server run at http://localhost:${port}/`)
});
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

16
exec.js Normal file
View File

@ -0,0 +1,16 @@
const process = require('child_process')
const workerProcess = process.spawn('sh', ['/Users/yaclty/WebstormProjects/deploy_server/routes/project/1/build.sh']);
let startTime = Date.now(); // 记录开始时间
workerProcess.stdout.on('data', function (execData) {
console.log('out:'+ execData)
});
workerProcess.stderr.on('data', function (execData) {
console.log('err', execData.toString())
});
workerProcess.on('close', function (code) {
// 计算使用时间
const useTime = Date.now() - startTime;
console.log('use time:', useTime);
});

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "deploy-server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"dev": "nodemon ./bin/www"
},
"dependencies": {
"body-parser": "^1.19.0",
"child_process": "^1.0.2",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"morgan": "~1.9.1"
}
}

268
public/index.html Normal file
View File

@ -0,0 +1,268 @@
<!doctype html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Deploy Service</title>
<link rel="stylesheet"
href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/index.css">
<style>
#app {
max-width: 1200px;
margin: auto;
}
</style>
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.js"
type="application/javascript"></script>
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/index.js"
type="application/javascript"></script>
</head>
<body>
<div id="app">
<h1>Deploy Service</h1>
<el-button @click="dialogVisible = true">新建</el-button>
<el-dialog :close-on-click-modal="false" title="新建部署项目" :visible.sync="dialogVisible" width="600">
<div>
<el-row>
<el-input v-model="project.name" placeholder="请输入项目名称"/>
</el-row>
<div style="margin-top:20px">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" placeholder="请输入待执行shell内容"
v-model="project.shell"/>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="handleCreate">确 定</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="项目运行日志" :visible.sync="historyVisible" width="500">
<div>
<el-table :data="currentHistories" stripe style="width: 100%;">
<el-table-column prop="time" label="创建时间"></el-table-column>
<el-table-column prop="useTime" label="执行时长" width="100">
<template slot-scope="scope">
<span>{{scope.row.useTime}} ms</span>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<span>{{formatStatus(scope.row, true)}}</span>
</template>
</el-table-column>
<el-table-column prop="lastUpdate" label="" width="100">
<template slot-scope="scope">
<a target="_blank"
:href="'./project/history?id=' + currentProject.id
+ '&time='+(scope.row.timestamp||scope.row.time)">查看记录</a>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination style="text-align: center;padding-top: 20px;"
:hide-on-single-page="true"
:total="currentHistory.list.length"
:page-size="currentHistory.size"
@current-change="onPageChange"
layout="prev, pager, next">
</el-pagination>
<!-- <span slot="footer" class="dialog-footer">-->
<!-- <el-button @click="dialogVisible = false">取 消</el-button>-->
<!-- <el-button type="primary" @click="handleCreate">确 定</el-button>-->
<!-- </span>-->
</el-dialog>
<el-dialog :close-on-click-modal="false" title="编辑脚本内容" :visible.sync="shellModify.visible" width="600">
<div>
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" placeholder="请输入待执行shell内容"
v-model="shellModify.shell"/>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="shellModify.visible = false">取 消</el-button>
<el-button :loading="shellModify.loading" type="primary" @click="handleModifyShell">确 定</el-button>
</span>
</el-dialog>
<div class="data-list">
<el-table :data="projects" stripe border style="width: 100%;margin-top:20px;" size="small">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="create" label="创建时间" width="180"></el-table-column>
<el-table-column prop="lastUpdate" label="更新时间" width="220"></el-table-column>
<el-table-column label="执行状态" width="100">
<template slot-scope="scope">
<span>{{formatStatus(scope.row)}}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250">
<template slot-scope="scope">
<el-button @click="showProjectInfo(scope.row)" type="text" size="small">执行历史</el-button>
<el-button @click="execShell(scope.row)" type="text" size="small">执行</el-button>
<el-button @click="editShell(scope.row)" type="text" size="small">编辑脚本</el-button>
<el-button @click="showHook(scope.row)" type="text" size="small">Hook地址</el-button>
</template>
</el-table-column>
</el-table>
<div class="log-list">
</div>
</div>
</div>
<script>
const app = new Vue({
el: '#app',
async mounted() {
let data = await this.requestData('./project/');
console.log(data);
this.projects = data
},
methods: {
/**
*
* @param {ProjectInfo} data
*/
formatStatus(data, isHistory = false) {
if (!isHistory && data.running) return '正在执行中';
else if ((data.lastStatus || data.status) == 'success') return '成功';
return (data.lastStatus || data.status);
},
requestData(url, data) {
return new Promise((resolve, reject) => {
let option = {}
if (data) {
option = {
method: 'POST',
mode: 'cors',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
}
const loading = this.$loading({
lock: true,
text: '加载中',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
fetch(url, option).then(response => response.json())
.then(data => {
loading.close()
if (data && data.code && data.code != 0) {
this.$message.error(data.message);
reject()
return;
}
resolve(data)
})
.catch(() => {
loading.close()
this.$message.error('加载数据失败')
reject()
})
})
},
async showProjectInfo(rowData) {
this.currentProject = rowData;
let histories = await this.requestData('./project/' + rowData.id);
this.currentHistory.list = histories;
this.historyVisible = true;
},
async handleCreate() {
let projects = await this.requestData('./project/create', this.project);
this.projects = projects;
this.project = {name: '', shell: ''};
this.dialogVisible = false;
},
getExecUrl(rowData) {
return location.href.replace(new RegExp('\\/$', 'g'), '') + '/project/hook/' + rowData.id + '?param=test';
},
execShell(rowData) {
this.$confirm('此操作会执行脚本, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
let data = await this.requestData(this.getExecUrl(rowData));
this.$message.info(data.message);
}).catch();
},
/**
*
* @param {ProjectInfo} rowData
* @returns {Promise<void>}
*/
async editShell(item) {
// this.$message.info('开发中...')
try {
const data = await this.requestData('./project/shell/' + item.id);
this.shellModify = {
visible: true,
loading: false,
shell: data.shell,
id: item.id
}
} catch (e) {
}
},
async handleModifyShell() {
this.shellModify.loading = true;
try {
const {id, shell} = this.shellModify
const data = await this.requestData('./project/shell/' + id, {shell});
this.$message.info('保存脚本内容成功')
this.shellModify.visible = false;
} catch (e) {
} finally {
this.shellModify.loading = false
}
},
showHook(rowData) {
this.$alert(this.getExecUrl(rowData), '请自行复制一下链接', {confirmButtonText: '关闭'});
},
onPageChange(page){
this.currentHistory.page = page;
}
},
computed: {
currentHistories() {
const {list, page, size} = this.currentHistory
const _list = [];
for (let start = 0, i = (page - 1) * size; i < list.length && start < size; i++, start++) {
_list.push(list[i]);
}
return _list;
}
},
data() {
return {
shellModify: {
visible: false,
shell: '',
loading: false,
id: 0,
},
dialogVisible: false,
historyVisible: false,
histories: [],
currentHistory: {
list: [],
size: 10,
page: 1
},
currentProject: {id: 0},
project: {
name: '',
shell: ''
},
projects: [],
}
}
})
</script>
</body>
</html>

View File

9
routes/index.js Normal file
View File

@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Projects' });
});
module.exports = router;

211
routes/project.js Normal file
View File

@ -0,0 +1,211 @@
const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');
const process = require('child_process');
const projectsFile = path.join(__dirname, 'project')
const getDatetime = (fmt = "YYYY-mm-dd HH:MM", date) => {
date = date || new Date();
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
}
}
;
return fmt;
}
router.get('/', function (req, res, next) {
const data = fs.readFileSync(projectsFile + '/index.json');
res.json(JSON.parse(data.toString()))
});
router.get('/history', (req, res, next) => {
let {id, time} = req.query;
const fileName = projectsFile + '/' + id + '/' + time + '.log';
if (!fs.existsSync(fileName)) {
res.json({code: 1, message: "日志不存在", fileName});
return;
}
res.sendFile(fileName, {}, function (err) {
if (err) {
next(err);
}
});
});
// 读取编译记录
router.get('/:id', function (req, res, next) {
const id = req.params.id;
const file = projectsFile + '/' + id + '/index.json';
if (!fs.existsSync(file)) {
res.json({code: 1, message: "项目不存在"});
return;
}
const data = fs.readFileSync(file);
res.json(JSON.parse(data.toString()))
});
router.get('/shell/:id', (req, res) => {
const path = projectsFile + '/' + req.params.id + '/build.sh';
if (!fs.existsSync(path)) {
res.send({code: 1, message: '脚本文件不存在'})
return;
}
const shell = fs.readFileSync(path,{encoding:'utf-8'})
res.send({code: 0, shell})
});
router.post('/shell/:id', (req, res) => {
const path = projectsFile + '/' + req.params.id + '/build.sh';
if (!fs.existsSync(path)) {
res.send({code: 1, message: '脚本文件文件不存在'})
return;
}
const shell = req.body.shell;
fs.writeFileSync(path, shell,{encoding:"utf-8"})
res.send({code: 0})
});
const writeExecLog = (id, status, running = false) => {
const data = JSON.parse(fs.readFileSync(projectsFile + '/index.json'));
data[id - 1].lastUpdate = getDatetime('YYYY-mm-dd HH:MM:SS');
data[id - 1].lastStatus = status;
data[id - 1].running = running;
fs.writeFile(projectsFile + '/index.json', JSON.stringify(data), (err1) => {
if (err1) {
console.log('writeExecLog:write project file fail')
}
})
}
// 执行
router.all('/hook/:id', function (req, res, next) {
const id = parseInt(req.params.id);
const projects = JSON.parse(fs.readFileSync(projectsFile + '/index.json',{encoding:'utf-8'}));
if (id < 1 || id > projects.length) {
res.json({code: 1, message: "项目不存在(1)"});
return;
}
if (projects[id - 1].running) {
res.json({code: 3, message: "当前项目脚本正在运行中"});
return;
}
const file = projectsFile + '/' + id + '/build.sh';
if (!fs.existsSync(file)) {
res.json({code: 1, message: "项目不存在(2)"});
return;
}
res.json({code: 0, message: "开始执行脚本,请稍后查看运行结果"});
// 历史记录
fs.readFile(projectsFile + '/' + id + '/index.json', (err, data) => {
if (err) {
console.log('read project {', id, '} history failed', err)
return;
}
const histories = JSON.parse(data.toString());
const currentTime = getDatetime('YYYY-mm-dd HH:MM:SS'),timeNow = Date.now();
// 开始执行脚本
fs.open(projectsFile + '/' + id + '/' + timeNow + '.log', 'a', function (err, fd) {
if (err) { // 操作日志失败了
writeExecLog(id, 'fail');
return;
}
writeExecLog(id, null, true); // 运行中
let isFailed = -1;
const workerProcess = process.spawn('sh', [file]);
let startTime = Date.now(); // 记录开始时间
workerProcess.stdout.on('data', (execData) => {
let line = execData.toString().trim();
console.log('out:', line);
if (line == 'execute success') {
isFailed = 1;
} else if (line == 'execute fail') {
isFailed = 0;
}
fs.writeSync(fd, line); // 写入日志
});
workerProcess.stderr.on('data', (execData) => {
console.log('err:', execData.toString())
fs.writeSync(fd, execData.toString()); // 写入日志
// isFailed = true;
});
workerProcess.on('close', function (code) {
// 计算使用时间
const useTime = Date.now() - startTime;
let status = isFailed == -1 ? '未知' : (isFailed == 1 ? '成功' : '失败');
histories.push({
time: currentTime,
timestamp:timeNow,
status,
useTime
})
// 写入记录
fs.writeFileSync(projectsFile + '/' + id + '/index.json', JSON.stringify(histories));
writeExecLog(id, status)
fs.close(fd, function (err) {
if (err) {
console.log('close file error', err);
}
});
});
});
});
});
//创建项目
router.post('/create', (req, res, next) => {
const data = JSON.parse(fs.readFileSync(projectsFile + '/index.json'));
let id = data.length + 1;
let {name, shell} = req.body;
if (!name || !shell) {
res.json({code: 1, message: "参数不能为空"})
return;
}
fs.mkdirSync(projectsFile + '/' + id); // 创建项目目录
// 创建记录文件
fs.writeFile(projectsFile + '/' + id + '/index.json', '[]', function (err) {
fs.writeFile(projectsFile + '/' + id + '/build.sh', shell, (err1) => {
if (err1) {
console.log('create shell success')
}
});
if (err) {
res.json({code: 2, message: "创建项目失败"})
}
data.push({
id,
name,
create: getDatetime(),
lastUpdate: null,
lastStatus: "",
running: false
})
fs.writeFile(projectsFile + '/index.json', JSON.stringify(data), (err1) => {
if (err1) {
console.log('create project fail')
}
})
res.json(data);
});
});
module.exports = router;

View File

@ -0,0 +1 @@
12123

10
routes/project/1/build.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
for ((j=1;j<=10;j++))
do
echo "$j"
if (( $j < 3 ))
then
sleep 1
fi
done

View File

@ -0,0 +1,3 @@
[{"time":"2020-02-23 18:01:16","status":"success","useTime":2012},{"time":"2020-02-23 18:06:00","status":"success","useTime":2016,
"timestamp": 123123
}]

View File

@ -0,0 +1 @@
[{"id":1,"name":"test","create":"2020-02-23 18:00","lastUpdate":"2020-02-23 18:06:02","lastStatus":"success","running":false}]

9
types.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
declare type ProjectInfo = {
create: String
id: number
lastStatus: "success" | String
status: "success" | String
lastUpdate: String
name: String
running: boolean
}

444
yarn.lock Normal file
View File

@ -0,0 +1,444 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.5:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
basic-auth@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
dependencies:
safe-buffer "5.1.2"
body-parser@1.18.3:
version "1.18.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=
dependencies:
bytes "3.0.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "~1.6.3"
iconv-lite "0.4.23"
on-finished "~2.3.0"
qs "6.5.2"
raw-body "2.3.3"
type-is "~1.6.16"
body-parser@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
dependencies:
bytes "3.1.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
child_process@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a"
integrity sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-parser@~1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188"
integrity sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==
dependencies:
cookie "0.3.1"
cookie-signature "1.0.6"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
debug@2.6.9, debug@~2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express@~4.16.1:
version "4.16.4"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
dependencies:
accepts "~1.3.5"
array-flatten "1.1.1"
body-parser "1.18.3"
content-disposition "0.5.2"
content-type "~1.0.4"
cookie "0.3.1"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.1.1"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.2"
path-to-regexp "0.1.7"
proxy-addr "~2.0.4"
qs "6.5.2"
range-parser "~1.2.0"
safe-buffer "5.1.2"
send "0.16.2"
serve-static "1.13.2"
setprototypeof "1.1.0"
statuses "~1.4.0"
type-is "~1.6.16"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.2"
statuses "~1.4.0"
unpipe "~1.0.0"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
iconv-lite@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
mime-types@~2.1.24:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
dependencies:
mime-db "1.43.0"
mime@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
morgan@~1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59"
integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==
dependencies:
basic-auth "~2.0.0"
debug "2.6.9"
depd "~1.1.2"
on-finished "~2.3.0"
on-headers "~1.0.1"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
on-headers@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
parseurl@~1.3.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
proxy-addr@~2.0.4:
version "2.0.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
qs@6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
range-parser@~1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==
dependencies:
bytes "3.0.0"
http-errors "1.6.3"
iconv-lite "0.4.23"
unpipe "1.0.0"
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.6.2"
mime "1.4.1"
ms "2.0.0"
on-finished "~2.3.0"
range-parser "~1.2.0"
statuses "~1.4.0"
serve-static@1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.2"
send "0.16.2"
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
statuses@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
type-is@~1.6.16, type-is@~1.6.17:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=