jj.js 0.8.8 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -21
- package/README.md +22 -551
- package/jj.js +14 -8
- package/jsconfig.json +9 -0
- package/jsdoc.conf.js +17 -0
- package/lib/app.js +61 -48
- package/lib/cache.js +118 -65
- package/lib/config.js +98 -94
- package/lib/context.js +22 -10
- package/lib/controller.js +34 -8
- package/lib/cookie.js +82 -40
- package/lib/ctx.js +5 -0
- package/lib/db.js +349 -10
- package/lib/loader.js +10 -4
- package/lib/logger.js +126 -47
- package/lib/middleware.js +58 -26
- package/lib/model.js +50 -1
- package/lib/pagination.js +221 -182
- package/lib/response.js +62 -8
- package/lib/router.js +3 -3
- package/lib/run.js +6 -6
- package/lib/tpl/exception.js +64 -64
- package/lib/tpl/jump.js +40 -40
- package/lib/tpl/types.js +22 -0
- package/lib/types.js +236 -0
- package/lib/upload.js +82 -5
- package/lib/url.js +19 -0
- package/lib/utils/date.js +50 -39
- package/lib/utils/error.js +8 -4
- package/lib/utils/fs.js +69 -78
- package/lib/utils/md5.js +9 -7
- package/lib/utils/str.js +20 -10
- package/lib/utils/utils.js +16 -10
- package/lib/view.js +82 -14
- package/package.json +11 -6
- package/types.js +306 -0
package/lib/utils/date.js
CHANGED
|
@@ -1,40 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @function
|
|
3
|
+
* @param {string} fmt - 格式化模板
|
|
4
|
+
* @param {(number|Date)} [date] - 时间戳或Date实例
|
|
5
|
+
* @returns {string} - 格式化后时间
|
|
6
|
+
*/
|
|
7
|
+
function format(fmt, date) {
|
|
8
|
+
(typeof date === 'string' || typeof date === 'number') && (date = new Date(parseInt(date + '000')));
|
|
9
|
+
date = date || new Date();
|
|
10
|
+
let ret;
|
|
11
|
+
let opt = {
|
|
12
|
+
"Y+": date.getFullYear().toString(), // 年
|
|
13
|
+
"y+": date.getFullYear().toString().slice(2),// 年
|
|
14
|
+
"m+": (date.getMonth() + 1).toString(), // 月
|
|
15
|
+
"d+": date.getDate().toString(), // 日
|
|
16
|
+
"H+": date.getHours().toString(), // 时
|
|
17
|
+
"h+": (date.getHours() > 12 ? date.getHours() - 12 : date.getHours()).toString(),// 时
|
|
18
|
+
"i+": date.getMinutes().toString(), // 分
|
|
19
|
+
"s+": date.getSeconds().toString() // 秒
|
|
20
|
+
};
|
|
21
|
+
for (let k in opt) {
|
|
22
|
+
ret = new RegExp("(" + k + ")").exec(fmt);
|
|
23
|
+
if (ret) {
|
|
24
|
+
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
return fmt;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @function - 获取多长时间前
|
|
32
|
+
* @param {number} time - 时间戳
|
|
33
|
+
* @returns {string} - 例如5分钟前
|
|
34
|
+
*/
|
|
35
|
+
function before(time) {
|
|
36
|
+
time -= 0;
|
|
37
|
+
let difTime = Date.now() / 1000 - time;
|
|
38
|
+
let { h, m } = { h: difTime / 3600, m: difTime / 60 };
|
|
39
|
+
let msg = "";
|
|
40
|
+
if (h < 1) {
|
|
41
|
+
msg = `${m}分钟前`;
|
|
42
|
+
} else if (h >= 1 && h <= 24) {
|
|
43
|
+
msg = `${h}小时前`;
|
|
44
|
+
} else if (h > 24) {
|
|
45
|
+
h = h / 24
|
|
46
|
+
msg = `${h}天前`;
|
|
47
|
+
}
|
|
48
|
+
return msg;
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
module.exports = {format, before};
|
package/lib/utils/error.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const fs = require('./fs.js');
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @function - 解析Error数据
|
|
5
|
+
* @param {Error} err - 错误对象
|
|
6
|
+
* @returns {object} - 解析后数据
|
|
7
|
+
*/
|
|
3
8
|
function parseError(err) {
|
|
4
9
|
let msg = err.message;
|
|
5
10
|
let stack = err.stack.replace(/(\r\n)/g, "\n").split("\n");
|
|
@@ -18,7 +23,7 @@ function parseError(err) {
|
|
|
18
23
|
let begin = row - 10;
|
|
19
24
|
let end = row + 10;
|
|
20
25
|
let nth = 0;
|
|
21
|
-
let code =
|
|
26
|
+
let code = [];
|
|
22
27
|
if(begin < 0) {
|
|
23
28
|
end -= begin;
|
|
24
29
|
begin = 0;
|
|
@@ -26,8 +31,7 @@ function parseError(err) {
|
|
|
26
31
|
if(file_path == 'anonymous' || !file_path || !fs.isFileSync(file_path)) {
|
|
27
32
|
code = stack;
|
|
28
33
|
} else {
|
|
29
|
-
code = require('fs').readFileSync(file_path);
|
|
30
|
-
code = code.toString().replace(/(\r\n)/g, "\n").split("\n");
|
|
34
|
+
code = require('fs').readFileSync(file_path).toString().replace(/(\r\n)/g, "\n").split("\n");
|
|
31
35
|
if(begin < 0) {
|
|
32
36
|
end -= begin;
|
|
33
37
|
begin = 0;
|
|
@@ -43,7 +47,7 @@ function parseError(err) {
|
|
|
43
47
|
nth = row - begin;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
return
|
|
50
|
+
return {
|
|
47
51
|
msg,
|
|
48
52
|
code,
|
|
49
53
|
stack,
|
package/lib/utils/fs.js
CHANGED
|
@@ -1,78 +1,69 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} else {
|
|
71
|
-
if(await fsFun.mkdirs(pt.dirname(dirname))) {
|
|
72
|
-
await fsFun.mkdir(dirname);
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = fsFun;
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const fsPromises = require('fs').promises;
|
|
3
|
+
const pt = require('path');
|
|
4
|
+
|
|
5
|
+
// 是否存在
|
|
6
|
+
const exists = async path => {
|
|
7
|
+
try {
|
|
8
|
+
await fsPromises.stat(path);
|
|
9
|
+
return true;
|
|
10
|
+
} catch(err) {
|
|
11
|
+
if(err.code === 'ENOENT') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
throw err;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 是否文件
|
|
19
|
+
const isFile = async path => {
|
|
20
|
+
try {
|
|
21
|
+
const stats = await fsPromises.stat(path);
|
|
22
|
+
return stats.isFile();
|
|
23
|
+
} catch(err) {
|
|
24
|
+
if(err.code === 'ENOENT') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const isFileSync = path => {
|
|
31
|
+
return fs.existsSync(path) && fs.statSync(path).isFile();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 是否目录
|
|
35
|
+
const isDir = async path => {
|
|
36
|
+
try {
|
|
37
|
+
const stats = await fsPromises.stat(path);
|
|
38
|
+
return stats.isDirectory();
|
|
39
|
+
} catch(err) {
|
|
40
|
+
if(err.code === 'ENOENT') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const isDirSync = path => {
|
|
47
|
+
return fs.existsSync(path) && fs.statSync(path).isDirectory();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 生成多级目录
|
|
51
|
+
const mkdirs = async function (dirname) {
|
|
52
|
+
if(await isDir(dirname)) {
|
|
53
|
+
return true;
|
|
54
|
+
} else {
|
|
55
|
+
if(await mkdirs(pt.dirname(dirname))) {
|
|
56
|
+
await fsPromises.mkdir(dirname);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
exists,
|
|
64
|
+
isFile,
|
|
65
|
+
isFileSync,
|
|
66
|
+
isDir,
|
|
67
|
+
isDirSync,
|
|
68
|
+
mkdirs
|
|
69
|
+
};
|
package/lib/utils/md5.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @function - md5
|
|
3
|
+
* @param {string} str
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
function md5(str) {
|
|
7
|
+
return require('crypto').createHash('md5').update(str).digest('hex');
|
|
8
|
+
}
|
|
9
|
+
module.exports = md5;
|
package/lib/utils/str.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @function - 转为驼峰
|
|
3
|
+
* @param {string} name
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
function toHump(name) {
|
|
7
|
+
return name.replace(/\_(\w)/g, function(all, letter){
|
|
8
|
+
return letter.toUpperCase();
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @function - 转为下划线
|
|
14
|
+
* @param {string} name
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function toLine(name) {
|
|
18
|
+
return name.replace(/(?!^)([A-Z])/g, "_$1").toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
module.exports = {toHump, toLine};
|
package/lib/utils/utils.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* jj.js内置工具集<br/>
|
|
3
|
+
* {date, error, fs, md5, str}
|
|
4
|
+
* @module utils
|
|
5
|
+
* @type {import('../../types').Utils}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = new Proxy({}, {
|
|
8
|
+
get: (target, prop) => {
|
|
9
|
+
if(prop in target || typeof prop == 'symbol' || prop == 'inspect') {
|
|
10
|
+
return target[prop];
|
|
11
|
+
}
|
|
12
|
+
if(prop == 'type') {
|
|
13
|
+
return para => Object.prototype.toString.call(para).slice(8, -1);
|
|
14
|
+
}
|
|
15
|
+
return require('./' + prop);
|
|
16
|
+
}
|
|
11
17
|
});
|
package/lib/view.js
CHANGED
|
@@ -1,35 +1,65 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const {
|
|
2
|
+
const {isFileSync} = require('./utils/fs');
|
|
3
|
+
const {readFile} = require('fs').promises;
|
|
3
4
|
const {app: cfg_app, view: cfg_view} = require('./config');
|
|
4
5
|
const {toLine} = require('./utils/str');
|
|
5
6
|
const Context = require('./context');
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @extends Context
|
|
10
|
+
*/
|
|
7
11
|
class View extends Context
|
|
8
12
|
{
|
|
13
|
+
/**
|
|
14
|
+
* Initialize a new `View`
|
|
15
|
+
* @public
|
|
16
|
+
* @param {import('../types').KoaCtx} ctx
|
|
17
|
+
*/
|
|
9
18
|
constructor(ctx) {
|
|
10
19
|
super(ctx);
|
|
11
20
|
this._data = {};
|
|
12
21
|
this.setEngine(cfg_view.view_engine);
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
/**
|
|
25
|
+
* 获取文件内容
|
|
26
|
+
* @public
|
|
27
|
+
* @param {string} [template]
|
|
28
|
+
* @returns {Promise<string>}
|
|
29
|
+
*/
|
|
16
30
|
async load(template) {
|
|
17
31
|
const file_name = this.parseTplName(template);
|
|
18
|
-
return await readFile(file_name);
|
|
32
|
+
return (await readFile(file_name)).toString();
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
/**
|
|
36
|
+
* 渲染(解析数据)内容
|
|
37
|
+
* @public
|
|
38
|
+
* @param {string} content
|
|
39
|
+
* @returns {Promise<string>}
|
|
40
|
+
*/
|
|
22
41
|
async render(content) {
|
|
23
42
|
return this._engine.render(content, this._data);
|
|
24
43
|
}
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
/**
|
|
46
|
+
* 渲染(解析数据)文件
|
|
47
|
+
* @public
|
|
48
|
+
* @param {string} [template]
|
|
49
|
+
* @returns {Promise<string>}
|
|
50
|
+
*/
|
|
27
51
|
async fetch(template) {
|
|
28
52
|
const file_name = this.parseTplName(template);
|
|
29
53
|
return await this._engine(file_name, this._data || {});
|
|
30
54
|
}
|
|
31
55
|
|
|
32
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 模版数据赋值
|
|
58
|
+
* @public
|
|
59
|
+
* @param {(string|object)} name
|
|
60
|
+
* @param {*} [value]
|
|
61
|
+
* @returns {this}
|
|
62
|
+
*/
|
|
33
63
|
assign(name, value) {
|
|
34
64
|
if(typeof name == 'string') {
|
|
35
65
|
this._data[name] = value;
|
|
@@ -39,13 +69,25 @@ class View extends Context
|
|
|
39
69
|
return this;
|
|
40
70
|
}
|
|
41
71
|
|
|
42
|
-
|
|
72
|
+
/**
|
|
73
|
+
* 获取模版数据
|
|
74
|
+
* @public
|
|
75
|
+
* @param {string} [name]
|
|
76
|
+
* @returns {object}
|
|
77
|
+
*/
|
|
43
78
|
data(name) {
|
|
44
79
|
if(name) return this._data[name];
|
|
45
80
|
else return this._data;
|
|
46
81
|
}
|
|
47
82
|
|
|
48
|
-
|
|
83
|
+
/**
|
|
84
|
+
* 解析模板地址
|
|
85
|
+
* @public
|
|
86
|
+
* @param {string} [template] - 默认当前请求方法名,支持智能解析
|
|
87
|
+
* @param {string} [view_folder] - 模板文件夹名字
|
|
88
|
+
* @param {string} [view_depr] - 模板文件分隔符
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
49
91
|
parsePath(template=this.ctx.ACTION, view_folder, view_depr) {
|
|
50
92
|
!view_folder && (view_folder = cfg_view.view_folder);
|
|
51
93
|
!view_depr && (view_depr = cfg_view.view_depr);
|
|
@@ -58,10 +100,15 @@ class View extends Context
|
|
|
58
100
|
}
|
|
59
101
|
}
|
|
60
102
|
path.extname(view_file) || (view_file += cfg_view.view_ext);
|
|
61
|
-
return path.join(
|
|
103
|
+
return path.join(cfg_app.base_dir, view_file);
|
|
62
104
|
}
|
|
63
105
|
|
|
64
|
-
|
|
106
|
+
/**
|
|
107
|
+
* 解析模板名字
|
|
108
|
+
* @public
|
|
109
|
+
* @param {string} [template] - 默认当前请求方法名,支持智能解析
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
65
112
|
parseTplName(template) {
|
|
66
113
|
let file_name = this.parsePath(template, this._folder, this._depr);
|
|
67
114
|
let is_file = isFileSync(file_name);
|
|
@@ -76,7 +123,12 @@ class View extends Context
|
|
|
76
123
|
return file_name;
|
|
77
124
|
}
|
|
78
125
|
|
|
79
|
-
|
|
126
|
+
/**
|
|
127
|
+
* 设置模版引擎
|
|
128
|
+
* @public
|
|
129
|
+
* @param {(string|object)} engine
|
|
130
|
+
* @returns {this}
|
|
131
|
+
*/
|
|
80
132
|
setEngine(engine) {
|
|
81
133
|
this._engine = typeof engine == 'string' ? require(engine) : engine;
|
|
82
134
|
this._engine.defaults.debug = cfg_app.app_debug;
|
|
@@ -87,7 +139,13 @@ class View extends Context
|
|
|
87
139
|
return this;
|
|
88
140
|
}
|
|
89
141
|
|
|
90
|
-
|
|
142
|
+
/**
|
|
143
|
+
* 设置模版函数
|
|
144
|
+
* @public
|
|
145
|
+
* @param {(string|object)} fun_obj - 函数名字或者包含多个函数的对象
|
|
146
|
+
* @param {*} [fun] - 函数
|
|
147
|
+
* @returns {this}
|
|
148
|
+
*/
|
|
91
149
|
setFilter(fun_obj, fun) {
|
|
92
150
|
if(typeof fun_obj === 'string') {
|
|
93
151
|
fun_obj = {[fun_obj]: fun};
|
|
@@ -99,13 +157,23 @@ class View extends Context
|
|
|
99
157
|
return this;
|
|
100
158
|
}
|
|
101
159
|
|
|
102
|
-
|
|
160
|
+
/**
|
|
161
|
+
* 设置模板目录
|
|
162
|
+
* @public
|
|
163
|
+
* @param {string} view_folder - 文件夹名字
|
|
164
|
+
* @returns {this}
|
|
165
|
+
*/
|
|
103
166
|
setFolder(view_folder) {
|
|
104
167
|
this._folder = view_folder;
|
|
105
168
|
return this;
|
|
106
169
|
}
|
|
107
170
|
|
|
108
|
-
|
|
171
|
+
/**
|
|
172
|
+
* 设置文件分割符
|
|
173
|
+
* @public
|
|
174
|
+
* @param {string} view_depr - 文件分隔符(默认'/',不分二级目录,可以设置为'_'或其他)
|
|
175
|
+
* @returns {this}
|
|
176
|
+
*/
|
|
109
177
|
setDepr(view_depr) {
|
|
110
178
|
this._depr = view_depr;
|
|
111
179
|
return this;
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jj.js",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A simple
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架)",
|
|
5
5
|
"main": "jj.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
},
|
|
6
|
+
"scripts": {},
|
|
8
7
|
"repository": {
|
|
9
8
|
"type": "git",
|
|
10
9
|
"url": "git+https://github.com/yafoo/jj.js.git"
|
|
@@ -21,13 +20,19 @@
|
|
|
21
20
|
},
|
|
22
21
|
"homepage": "https://github.com/yafoo/jj.js#readme",
|
|
23
22
|
"dependencies": {
|
|
24
|
-
"@koa/router": "^
|
|
23
|
+
"@koa/router": "^12.0.1",
|
|
25
24
|
"art-template": "^4.13.2",
|
|
26
25
|
"escape-html": "^1.0.3",
|
|
27
26
|
"is-class": "0.0.9",
|
|
28
|
-
"koa": "^2.
|
|
27
|
+
"koa": "^2.15.0",
|
|
29
28
|
"koa-body": "^5.0.0",
|
|
30
29
|
"koa-static": "^5.0.0",
|
|
31
30
|
"mysql": "^2.18.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"watch": "^1.0.2"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">= 12.17.0"
|
|
32
37
|
}
|
|
33
38
|
}
|