lack 1.3.17 → 1.4.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/README-en_US.md +55 -0
- package/README.md +31 -18
- package/assets/js/lib/auth.js +35 -2
- package/assets/js/lib/resRulesServer.js +8 -1
- package/assets/js/lib/resStatsServer.js +15 -3
- package/assets/js/lib/rulesServer.js +8 -1
- package/assets/js/lib/sniCallback.js +16 -2
- package/assets/js/lib/statsServer.js +13 -4
- package/assets/js/lib/uiServer/index.js +2 -1
- package/assets/js/lib/uiServer/router.js +1 -1
- package/assets/js/package.json +3 -2
- package/assets/ts/package.json +5 -3
- package/assets/ts/src/auth.ts +35 -1
- package/assets/ts/src/resRulesServer.ts +8 -1
- package/assets/ts/src/resStatsServer.ts +14 -2
- package/assets/ts/src/rulesServer.ts +8 -1
- package/assets/ts/src/sniCallback.ts +16 -2
- package/assets/ts/src/statsServer.ts +12 -3
- package/assets/ts/src/types/global.d.ts +8 -4
- package/assets/ts/src/uiServer/index.ts +1 -1
- package/assets/ts/src/uiServer/router.ts +2 -2
- package/assets/ts/tsconfig.json +1 -0
- package/bin/index.js +3 -3
- package/bin/init.js +292 -109
- package/bin/watch.js +17 -11
- package/package.json +4 -5
package/README-en_US.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# lack
|
|
2
|
+
[](https://npmjs.org/package/lack)
|
|
3
|
+
[](http://nodejs.org/download/)
|
|
4
|
+
[](https://npmjs.org/package/lack)
|
|
5
|
+
[](https://www.npmjs.com/package/lack)
|
|
6
|
+
[](https://www.npmjs.com/package/lack)
|
|
7
|
+
|
|
8
|
+
[中文](./README.md) · English
|
|
9
|
+
|
|
10
|
+
A scaffolding tool for rapid [Whistle](https://github.com/avwo/whistle) plugin development.
|
|
11
|
+
|
|
12
|
+
### Installation
|
|
13
|
+
```sh
|
|
14
|
+
npm i -g lack
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Usage
|
|
18
|
+
1. Create plugin directory
|
|
19
|
+
```sh
|
|
20
|
+
mkdir whistle.your-plugin-name
|
|
21
|
+
cd whistle.your-plugin-name
|
|
22
|
+
```
|
|
23
|
+
> Note: Plugin name must follow `whistle.xxx` or `@scope/whistle.xxx` format where `xxx` can only contain `a-z`, `0-9`, `-` and `_` (underscore not recommended)
|
|
24
|
+
|
|
25
|
+
2. Initialize project
|
|
26
|
+
- Interactive mode: `lack init`
|
|
27
|
+
> Prompts to select required plugin hooks (multiple selection supported)
|
|
28
|
+
- Quick command (`lack init hook1,hook2...`): [Plugin Development](https://wproxy.org/docs/extensions/dev.html)
|
|
29
|
+
|
|
30
|
+
3. Install dependencies
|
|
31
|
+
```sh
|
|
32
|
+
npm i
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. [Optional] Code style setup
|
|
36
|
+
```sh
|
|
37
|
+
npx install-peerdeps --dev eslint-config-airbnb
|
|
38
|
+
```
|
|
39
|
+
> Configuration reference: https://www.npmjs.com/package/eslint-config-airbnb
|
|
40
|
+
|
|
41
|
+
5. Development mode
|
|
42
|
+
```sh
|
|
43
|
+
lack watch
|
|
44
|
+
```
|
|
45
|
+
Features:
|
|
46
|
+
- Auto-reloads plugin into running Whistle instance
|
|
47
|
+
- Automatically reloads on code changes
|
|
48
|
+
- Displays plugin `console.xxx` output in terminal
|
|
49
|
+
|
|
50
|
+
6. View help
|
|
51
|
+
```sh
|
|
52
|
+
lack --help
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Complete development guide: [Plugin Development](https://wproxy.org/docs/extensions/dev.html)
|
package/README.md
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/lack)
|
|
6
6
|
[](https://www.npmjs.com/package/lack)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
中文 · [English](./README-en_US.md)
|
|
9
|
+
|
|
10
|
+
用于快速生成 [Whistle](https://github.com/avwo/whistle) 插件的脚手架工具。
|
|
9
11
|
|
|
10
12
|
### 安装
|
|
11
13
|
``` sh
|
|
@@ -13,25 +15,36 @@ npm i -g lack
|
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
### 使用
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
1. 创建插件目录
|
|
19
|
+
``` sh
|
|
20
|
+
mkdir whistle.your-plugin-name
|
|
21
|
+
cd whistle.your-plugin-name
|
|
22
|
+
```
|
|
23
|
+
> 注意:插件名必须符合 `whistle.xxx` 或 `@scope/whistle.xxx` 格式,其中 `xxx` 只能包含 `a-z`、`0-9`、`-` 和 `_`(下划线不推荐使用)
|
|
24
|
+
2. 初始化项目
|
|
25
|
+
- 手动选择:`lack init`
|
|
26
|
+
> 该命令会交互式询问你需要哪些插件钩子(支持多选),按需选择即可
|
|
27
|
+
- 快捷命令(`lack init hook1,hook2...`):[插件开发](https://wproxy.org/docs/extensions/dev.html)
|
|
28
|
+
3. 安装依赖
|
|
29
|
+
``` sh
|
|
30
|
+
npm i
|
|
31
|
+
```
|
|
32
|
+
4. 【可选】代码规范配置
|
|
26
33
|
``` sh
|
|
27
|
-
|
|
28
|
-
w2 run
|
|
34
|
+
npx install-peerdeps --dev eslint-config-airbnb
|
|
29
35
|
```
|
|
30
|
-
>
|
|
31
|
-
|
|
32
|
-
```sh
|
|
36
|
+
> 详细配置参考:https://www.npmjs.com/package/eslint-config-airbnb
|
|
37
|
+
5. 开发模式
|
|
38
|
+
``` sh
|
|
33
39
|
lack watch
|
|
34
40
|
```
|
|
35
|
-
|
|
41
|
+
该命令的功能:
|
|
42
|
+
- 自动重新加载插件到运行的 Whistle 实例
|
|
43
|
+
- 插件代码变更时会自动重新加载
|
|
44
|
+
- 可以在命令行查看插件 `console.xxx` 输出的日志
|
|
45
|
+
6. 查看帮助
|
|
46
|
+
``` sh
|
|
47
|
+
lack --help
|
|
48
|
+
```
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
完整插件开发流程参考文档:[插件开发](https://wproxy.org/docs/extensions/dev.html)
|
package/assets/js/lib/auth.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
|
-
|
|
2
1
|
module.exports = async (req, options) => {
|
|
2
|
+
/**
|
|
3
|
+
const { fullUrl } = req;
|
|
4
|
+
// Returns 403 Forbidden status code if URL contains '/test/forbidden'
|
|
5
|
+
if (fullUrl.includes('/test/forbidden')) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
// Returns 403 status code with custom HTML message if URL contains '/test/message/forbidden'
|
|
9
|
+
if (fullUrl.includes('/test/message/forbidden')) {
|
|
10
|
+
req.setHtml('<strong>Access Denied</strong>');
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Requires username/password authentication if URL contains '/test/login'
|
|
15
|
+
if (fullUrl.includes('/test/login')) {
|
|
16
|
+
const auth = req.headers.authorization || req.headers['proxy-authorization'];
|
|
17
|
+
if (auth) {
|
|
18
|
+
// TODO: Validate username and password - return true if valid, false otherwise
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
req.setLogin(true); // Triggers basic auth prompt
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Returns 302 redirect if URL contains '/test/redirect'
|
|
26
|
+
if (fullUrl.includes('/test/redirect')) {
|
|
27
|
+
req.setRedirect('https://www.example.com/test');
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// All other requests are allowed by default
|
|
32
|
+
// Custom headers can be added using `req.setHeader`
|
|
33
|
+
// Only headers prefixed with 'x-whistle-' are supported
|
|
34
|
+
// Example: req.setHeader('x-whistle-xxx', 'value');
|
|
3
35
|
req.setHeader('x-whistle-custom-header', 'lack');
|
|
4
|
-
|
|
36
|
+
**/
|
|
37
|
+
return true;
|
|
5
38
|
};
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
server.on('request', (req
|
|
4
|
-
|
|
2
|
+
export default (server, options) => {
|
|
3
|
+
server.on('request', (req) => {
|
|
4
|
+
const { originalReq, originalRes } = req;
|
|
5
|
+
console.log('Value:', originalReq.ruleValue);
|
|
6
|
+
console.log('URL:', originalReq.fullUrl);
|
|
7
|
+
console.log('Method:', originalReq.method);
|
|
8
|
+
console.log('Server IP', originalRes.serverIp);
|
|
9
|
+
console.log('Status Code:', originalRes.statusCode);
|
|
10
|
+
console.log('Response Headers:', originalReq.headers);
|
|
11
|
+
// get session data
|
|
12
|
+
req.getSession((reqSession) => {
|
|
13
|
+
if (reqSession) {
|
|
14
|
+
console.log('Response Body:', reqSession.res.body);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
5
17
|
});
|
|
6
18
|
};
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
// sniCallback plugin handler - Dynamically controls TLS tunneling behavior based on request URL
|
|
2
2
|
module.exports = async (req, options) => {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
const { fullUrl, originalReq } = req;
|
|
5
|
+
// Preserve TLS encryption for specific domains (skip MITM decryption)
|
|
6
|
+
if (fullUrl === 'https://tunnel.example.com' || originalReq.sniValue === 'tunnel') {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Use custom certificate for targeted decryption
|
|
11
|
+
// Supports .crt, .pem, .cer certificate formats
|
|
12
|
+
if (fullUrl === 'https://custom.example.com') {
|
|
13
|
+
return { key, cert };
|
|
14
|
+
}
|
|
15
|
+
**/
|
|
16
|
+
// Default behavior: Decrypt TLS using Whistle's built-in certificate
|
|
17
|
+
return true;
|
|
4
18
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export default (server, options) => {
|
|
2
|
+
server.on('request', (req) => {
|
|
3
|
+
const { originalReq } = req;
|
|
4
|
+
console.log('Value:', originalReq.ruleValue);
|
|
5
|
+
console.log('URL:', originalReq.fullUrl);
|
|
6
|
+
console.log('Method:', originalReq.method);
|
|
7
|
+
console.log('Request Headers:', originalReq.headers);
|
|
8
|
+
// get request session
|
|
9
|
+
req.getReqSession((reqSession) => {
|
|
10
|
+
if (reqSession) {
|
|
11
|
+
console.log('Request Body:', reqSession.req.body);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
5
14
|
});
|
|
6
15
|
};
|
|
@@ -3,13 +3,14 @@ const bodyParser = require('koa-bodyparser');
|
|
|
3
3
|
const onerror = require('koa-onerror');
|
|
4
4
|
const serve = require('koa-static');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const
|
|
6
|
+
const Router = require('@koa/router');
|
|
7
7
|
const setupRouter = require('./router');
|
|
8
8
|
|
|
9
9
|
const MAX_AGE = 1000 * 60 * 5;
|
|
10
10
|
|
|
11
11
|
module.exports = (server, options) => {
|
|
12
12
|
const app = new Koa();
|
|
13
|
+
const router = new Router();
|
|
13
14
|
app.proxy = true;
|
|
14
15
|
app.silent = true;
|
|
15
16
|
onerror(app);
|
package/assets/js/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle.lack",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "",
|
|
5
|
+
"whistleConfig": {},
|
|
5
6
|
"dependencies": {
|
|
7
|
+
"@koa/router": "^13.1.1",
|
|
6
8
|
"koa": "^2.15.0",
|
|
7
9
|
"koa-bodyparser": "^4.4.1",
|
|
8
10
|
"koa-onerror": "^4.2.0",
|
|
9
|
-
"koa-router": "^12.0.1",
|
|
10
11
|
"koa-static": "^5.0.0"
|
|
11
12
|
}
|
|
12
13
|
}
|
package/assets/ts/package.json
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle.lack",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "tsc -w"
|
|
7
7
|
},
|
|
8
|
+
"whistleConfig": {},
|
|
8
9
|
"dependencies": {
|
|
9
10
|
"koa": "^2.15.0",
|
|
10
11
|
"koa-bodyparser": "^4.4.1",
|
|
11
12
|
"koa-onerror": "^4.2.0",
|
|
12
|
-
"koa
|
|
13
|
+
"@koa/router": "^13.1.1",
|
|
13
14
|
"koa-static": "^5.0.0"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
17
|
+
"@types/node": "^24.0.14",
|
|
16
18
|
"typescript": "^4.6.2"
|
|
17
19
|
},
|
|
18
20
|
"tsTypes": {
|
|
19
21
|
"@types/koa": "^2.13.4",
|
|
22
|
+
"@types/koa__router": "^12.0.4",
|
|
20
23
|
"@types/koa-bodyparser": "^4.3.5",
|
|
21
|
-
"@types/koa-router": "^7.4.4",
|
|
22
24
|
"@types/koa-static": "^4.0.2"
|
|
23
25
|
}
|
|
24
26
|
}
|
package/assets/ts/src/auth.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
|
|
2
2
|
export default async (req: Whistle.PluginAuthRequest, options: Whistle.PluginOptions) => {
|
|
3
|
+
/**
|
|
4
|
+
const { fullUrl } = req;
|
|
5
|
+
// Returns 403 Forbidden status code if URL contains '/test/forbidden'
|
|
6
|
+
if (fullUrl.includes('/test/forbidden')) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
// Returns 403 status code with custom HTML message if URL contains '/test/message/forbidden'
|
|
10
|
+
if (fullUrl.includes('/test/message/forbidden')) {
|
|
11
|
+
req.setHtml('<strong>Access Denied</strong>');
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Requires username/password authentication if URL contains '/test/login'
|
|
16
|
+
if (fullUrl.includes('/test/login')) {
|
|
17
|
+
const auth = req.headers.authorization || req.headers['proxy-authorization'];
|
|
18
|
+
if (auth) {
|
|
19
|
+
// TODO: Validate username and password - return true if valid, false otherwise
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
req.setLogin(true); // Triggers basic auth prompt
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Returns 302 redirect if URL contains '/test/redirect'
|
|
27
|
+
if (fullUrl.includes('/test/redirect')) {
|
|
28
|
+
req.setRedirect('https://www.example.com/test');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// All other requests are allowed by default
|
|
33
|
+
// Custom headers can be added using `req.setHeader`
|
|
34
|
+
// Only headers prefixed with 'x-whistle-' are supported
|
|
35
|
+
// Example: req.setHeader('x-whistle-xxx', 'value');
|
|
3
36
|
req.setHeader('x-whistle-custom-header', 'lack');
|
|
4
|
-
|
|
37
|
+
**/
|
|
38
|
+
return true;
|
|
5
39
|
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {
|
|
3
3
|
server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {
|
|
4
|
-
|
|
4
|
+
// rules & values
|
|
5
|
+
// res.end(JSON.stringify({
|
|
6
|
+
// rules: '',
|
|
7
|
+
// values: {},
|
|
8
|
+
// }));
|
|
9
|
+
|
|
10
|
+
// rules
|
|
11
|
+
res.end('');
|
|
5
12
|
});
|
|
6
13
|
};
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
|
|
2
2
|
export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {
|
|
3
|
-
server.on('request', (req: Whistle.PluginRequest
|
|
4
|
-
|
|
3
|
+
server.on('request', (req: Whistle.PluginRequest) => {
|
|
4
|
+
const { originalReq, originalRes } = req;
|
|
5
|
+
console.log('Value:', originalReq.ruleValue);
|
|
6
|
+
console.log('URL:', originalReq.fullUrl);
|
|
7
|
+
console.log('Method:', originalReq.method);
|
|
8
|
+
console.log('Server IP', originalRes.serverIp);
|
|
9
|
+
console.log('Status Code:', originalRes.statusCode);
|
|
10
|
+
console.log('Response Headers:', originalReq.headers);
|
|
11
|
+
// get session data
|
|
12
|
+
req.getSession((reqSession) => {
|
|
13
|
+
if (reqSession) {
|
|
14
|
+
console.log('Response Body:', reqSession.res.body);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
5
17
|
});
|
|
6
18
|
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {
|
|
3
3
|
server.on('request', (req: Whistle.PluginRequest, res: Whistle.PluginResponse) => {
|
|
4
|
-
|
|
4
|
+
// rules & values
|
|
5
|
+
// res.end(JSON.stringify({
|
|
6
|
+
// rules: '',
|
|
7
|
+
// values: {},
|
|
8
|
+
// }));
|
|
9
|
+
|
|
10
|
+
// rules
|
|
11
|
+
res.end('');
|
|
5
12
|
});
|
|
6
13
|
};
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
// sniCallback plugin handler - Dynamically controls TLS tunneling behavior based on request URL
|
|
2
2
|
export default async (req: Whistle.PluginSNIRequest, options: Whistle.PluginOptions) => {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
const { fullUrl } = req;
|
|
5
|
+
// Preserve TLS encryption for specific domains (skip MITM decryption)
|
|
6
|
+
if (fullUrl === 'https://tunnel.example.com') {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Use custom certificate for targeted decryption
|
|
11
|
+
// Supports .crt, .pem, .cer certificate formats
|
|
12
|
+
if (fullUrl === 'https://custom.example.com') {
|
|
13
|
+
return { key, cert };
|
|
14
|
+
}
|
|
15
|
+
**/
|
|
16
|
+
// Default behavior: Decrypt TLS using Whistle's built-in certificate
|
|
17
|
+
return true;
|
|
4
18
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
1
|
export default (server: Whistle.PluginServer, options: Whistle.PluginOptions) => {
|
|
3
|
-
server.on('request', (req: Whistle.PluginRequest
|
|
4
|
-
|
|
2
|
+
server.on('request', (req: Whistle.PluginRequest) => {
|
|
3
|
+
const { originalReq } = req;
|
|
4
|
+
console.log('Value:', originalReq.ruleValue);
|
|
5
|
+
console.log('URL:', originalReq.fullUrl);
|
|
6
|
+
console.log('Method:', originalReq.method);
|
|
7
|
+
console.log('Request Headers:', originalReq.headers);
|
|
8
|
+
// get request session
|
|
9
|
+
req.getReqSession((reqSession) => {
|
|
10
|
+
if (reqSession) {
|
|
11
|
+
console.log('Request Body:', reqSession.req.body);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
5
14
|
});
|
|
6
15
|
};
|
|
@@ -200,6 +200,7 @@ declare namespace Whistle {
|
|
|
200
200
|
sharedStorage: SharedStorage;
|
|
201
201
|
baseUrl: string;
|
|
202
202
|
LRU: LRUCache;
|
|
203
|
+
zipBody(body: any, stream: WhistleBase.Request | WhistleBase.Response, cb: (result: Buffer | '') => void): void;
|
|
203
204
|
getValue(key: string, cb: (value: string) => void): void;
|
|
204
205
|
getCert(domain: string, cb: (cert: any) => void): void;
|
|
205
206
|
getRootCA(cb: (cert: any) => void): void;
|
|
@@ -243,13 +244,13 @@ declare namespace Whistle {
|
|
|
243
244
|
type PassThrough = (uri?: PassThroughReq, trailers?: PassThroughRes) => void;
|
|
244
245
|
|
|
245
246
|
interface WriteHead {
|
|
246
|
-
(code
|
|
247
|
-
(code
|
|
247
|
+
(code?: string | number, msg?: string, headers?: any): void;
|
|
248
|
+
(code?: string | number, headers?: any): void;
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
interface RequestFn {
|
|
251
|
-
(uri
|
|
252
|
-
(uri
|
|
252
|
+
(uri?: any, cb?: (res: any) => void, opts?: any): any;
|
|
253
|
+
(uri?: any, opts?: any, cb?: (res: any) => void): any;
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
class PluginRequest extends WhistleBase.Request {
|
|
@@ -309,6 +310,8 @@ declare namespace Whistle {
|
|
|
309
310
|
isRexExp?: boolean;
|
|
310
311
|
pattern?: string;
|
|
311
312
|
customParser?: boolean | '';
|
|
313
|
+
serverIp: string;
|
|
314
|
+
statusCode: string;
|
|
312
315
|
};
|
|
313
316
|
originalRes: {
|
|
314
317
|
serverIp: string;
|
|
@@ -338,6 +341,7 @@ declare namespace Whistle {
|
|
|
338
341
|
setReqRules: SetRules;
|
|
339
342
|
setResRules: SetRules;
|
|
340
343
|
disableTrailers?: boolean;
|
|
344
|
+
writeHead: WriteHead;
|
|
341
345
|
}
|
|
342
346
|
class PluginUIRequest extends WhistleBase.Request {
|
|
343
347
|
clientIp: string;
|
|
@@ -3,7 +3,7 @@ import bodyParser from 'koa-bodyparser';
|
|
|
3
3
|
import onerror from 'koa-onerror';
|
|
4
4
|
import serve from 'koa-static';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import Router from 'koa
|
|
6
|
+
import Router from '@koa/router';
|
|
7
7
|
import setupRouter from './router';
|
|
8
8
|
|
|
9
9
|
const MAX_AGE = 1000 * 60 * 5;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Router from 'koa
|
|
1
|
+
import Router from '@koa/router';
|
|
2
2
|
|
|
3
|
-
// For help see https://github.com/
|
|
3
|
+
// For help, see https://github.com/koajs/router
|
|
4
4
|
export default (router: Router) => {
|
|
5
5
|
// router.get('/cgi-bin/init', (ctx) => {
|
|
6
6
|
// ctx.body = 'Hello whistle.';
|
package/assets/ts/tsconfig.json
CHANGED
package/bin/index.js
CHANGED
|
@@ -8,11 +8,11 @@ let bingo;
|
|
|
8
8
|
|
|
9
9
|
program
|
|
10
10
|
.version(conf.version)
|
|
11
|
-
.command('init')
|
|
11
|
+
.command('init [hooks]')
|
|
12
12
|
.description('create whistle plugin project.')
|
|
13
|
-
.action(() => {
|
|
13
|
+
.action((hooks) => {
|
|
14
14
|
bingo = true;
|
|
15
|
-
init();
|
|
15
|
+
init(hooks);
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
program
|
package/bin/init.js
CHANGED
|
@@ -5,9 +5,12 @@ const path = require('path');
|
|
|
5
5
|
/* eslint-disable no-sync */
|
|
6
6
|
let type = 'ts';
|
|
7
7
|
let srcDir = 'src';
|
|
8
|
+
let curHooks = {};
|
|
9
|
+
let curType;
|
|
10
|
+
let hasHooks;
|
|
8
11
|
const ROOT = path.join(__dirname, '../');
|
|
9
12
|
const NAME_RE = /^(@[a-z\d_-]+\/)?(whistle\.)?([a-z\d_-]+)$/;
|
|
10
|
-
const NAME_TIPS = '
|
|
13
|
+
const NAME_TIPS = 'Plugin name must match either \'whistle.[a-z0-9_-]+\' or \'@scope/whistle.[a-z0-9_-]+\'';
|
|
11
14
|
const TS_PKG = fse.readJSONSync(path.join(ROOT, 'assets/ts/package.json'));
|
|
12
15
|
const JS_PKG = fse.readJSONSync(path.join(ROOT, 'assets/js/package.json'));
|
|
13
16
|
const TEMPLATES = [
|
|
@@ -43,13 +46,13 @@ const getPackage = () => {
|
|
|
43
46
|
try {
|
|
44
47
|
pkg = fse.readJSONSync('package.json');
|
|
45
48
|
} catch (e) {}
|
|
46
|
-
return
|
|
49
|
+
return { ...pkg };
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
const copySync = (src, dest) => {
|
|
50
|
-
|
|
51
|
-
if (!fs.existsSync(
|
|
52
|
-
fse.copySync(path.join(ROOT, src),
|
|
53
|
+
const d = dest || src;
|
|
54
|
+
if (!fs.existsSync(d)) {
|
|
55
|
+
fse.copySync(path.join(ROOT, src), d);
|
|
53
56
|
}
|
|
54
57
|
};
|
|
55
58
|
|
|
@@ -69,7 +72,7 @@ const readIndexFile = () => {
|
|
|
69
72
|
};
|
|
70
73
|
|
|
71
74
|
const selectTemplate = async () => {
|
|
72
|
-
const
|
|
75
|
+
const template = curType ? TEMPLATES[curType === 'ts' ? 0 : 1] : (await inquirer.prompt([
|
|
73
76
|
{
|
|
74
77
|
type: 'list',
|
|
75
78
|
name: 'template',
|
|
@@ -77,7 +80,7 @@ const selectTemplate = async () => {
|
|
|
77
80
|
message: 'Select template:',
|
|
78
81
|
choices: TEMPLATES,
|
|
79
82
|
},
|
|
80
|
-
]);
|
|
83
|
+
])).template;
|
|
81
84
|
if (template === 'TypeScript') {
|
|
82
85
|
return template;
|
|
83
86
|
}
|
|
@@ -87,105 +90,132 @@ const selectTemplate = async () => {
|
|
|
87
90
|
};
|
|
88
91
|
|
|
89
92
|
const selectAuth = async () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
let { auth } = curHooks;
|
|
94
|
+
if (!hasHooks) {
|
|
95
|
+
auth = (await inquirer.prompt([
|
|
96
|
+
{
|
|
97
|
+
type: 'confirm',
|
|
98
|
+
name: 'auth',
|
|
99
|
+
default: false,
|
|
100
|
+
message: 'Do you need auth function?',
|
|
101
|
+
},
|
|
102
|
+
])).auth;
|
|
103
|
+
}
|
|
98
104
|
return auth && `${srcDir}/auth.${type}`;
|
|
99
105
|
};
|
|
100
106
|
|
|
101
107
|
const selectSni = async () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
let { sni } = curHooks;
|
|
109
|
+
if (!hasHooks) {
|
|
110
|
+
sni = (await inquirer.prompt([
|
|
111
|
+
{
|
|
112
|
+
type: 'confirm',
|
|
113
|
+
name: 'sni',
|
|
114
|
+
default: false,
|
|
115
|
+
message: 'Do you need sniCallback function?',
|
|
116
|
+
},
|
|
117
|
+
])).sni;
|
|
118
|
+
}
|
|
110
119
|
return sni && `${srcDir}/sniCallback.${type}`;
|
|
111
120
|
};
|
|
112
121
|
|
|
113
122
|
const selectUIServer = async () => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
let { uiServer } = curHooks;
|
|
124
|
+
if (!hasHooks) {
|
|
125
|
+
uiServer = (await inquirer.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'confirm',
|
|
128
|
+
name: 'uiServer',
|
|
129
|
+
default: false,
|
|
130
|
+
message: 'Do you need uiServer?',
|
|
131
|
+
},
|
|
132
|
+
])).uiServer;
|
|
133
|
+
}
|
|
122
134
|
return uiServer && `${srcDir}/uiServer`;
|
|
123
135
|
};
|
|
124
136
|
|
|
125
137
|
const setHookFile = (hooks) => {
|
|
126
138
|
const servers = {};
|
|
127
|
-
hooks
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
if (hooks) {
|
|
140
|
+
hooks.forEach((hook) => {
|
|
141
|
+
servers[hook] = `${srcDir}/${hook}.${type}`;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
130
144
|
return servers;
|
|
131
145
|
};
|
|
132
146
|
|
|
133
147
|
const selectRulesServers = async () => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
let { rulesServers } = curHooks;
|
|
149
|
+
if (!hasHooks) {
|
|
150
|
+
rulesServers = (await inquirer.prompt([
|
|
151
|
+
{
|
|
152
|
+
type: 'checkbox',
|
|
153
|
+
name: 'rulesServers',
|
|
154
|
+
message: 'Select rules servers:',
|
|
155
|
+
choices: RULES_SERVERS,
|
|
156
|
+
},
|
|
157
|
+
])).rulesServers;
|
|
158
|
+
}
|
|
142
159
|
return setHookFile(rulesServers);
|
|
143
160
|
};
|
|
144
161
|
|
|
145
162
|
const selectStatsServers = async () => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
let { statsServers } = curHooks;
|
|
164
|
+
if (!hasHooks) {
|
|
165
|
+
statsServers = (await inquirer.prompt([
|
|
166
|
+
{
|
|
167
|
+
type: 'checkbox',
|
|
168
|
+
name: 'statsServers',
|
|
169
|
+
message: 'Select stats servers:',
|
|
170
|
+
choices: STATS_SERVERS,
|
|
171
|
+
},
|
|
172
|
+
])).statsServers;
|
|
173
|
+
}
|
|
154
174
|
return setHookFile(statsServers);
|
|
155
175
|
};
|
|
156
176
|
|
|
157
177
|
const selectPipeServers = async () => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
let { pipeServers } = curHooks;
|
|
179
|
+
if (!hasHooks) {
|
|
180
|
+
pipeServers = (await inquirer.prompt([
|
|
181
|
+
{
|
|
182
|
+
type: 'checkbox',
|
|
183
|
+
name: 'pipeServers',
|
|
184
|
+
message: 'Select pipe servers:',
|
|
185
|
+
choices: PIPE_SERVERS,
|
|
186
|
+
},
|
|
187
|
+
])).pipeServers;
|
|
188
|
+
}
|
|
166
189
|
const hooks = [];
|
|
167
|
-
pipeServers
|
|
168
|
-
hook
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
if (pipeServers) {
|
|
191
|
+
pipeServers.join('+').split('+').forEach((hook) => {
|
|
192
|
+
const h = hook.trim();
|
|
193
|
+
if (h) {
|
|
194
|
+
hooks.push(h);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
173
198
|
return setHookFile(hooks);
|
|
174
199
|
};
|
|
175
200
|
|
|
176
201
|
const selectRulesFiles = async () => {
|
|
202
|
+
let { rulesFiles } = curHooks;
|
|
203
|
+
if (!hasHooks) {
|
|
204
|
+
rulesFiles = (await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'checkbox',
|
|
207
|
+
name: 'rulesFiles',
|
|
208
|
+
message: 'Select rules files:',
|
|
209
|
+
choices: RULES_FILES,
|
|
210
|
+
},
|
|
211
|
+
])).rulesFiles;
|
|
212
|
+
}
|
|
177
213
|
const result = {};
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
choices: RULES_FILES,
|
|
184
|
-
},
|
|
185
|
-
]);
|
|
186
|
-
rulesFiles.forEach((hook) => {
|
|
187
|
-
result[hook] = hook;
|
|
188
|
-
});
|
|
214
|
+
if (rulesFiles) {
|
|
215
|
+
rulesFiles.forEach((hook) => {
|
|
216
|
+
result[hook] = hook;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
189
219
|
return result;
|
|
190
220
|
};
|
|
191
221
|
|
|
@@ -198,59 +228,201 @@ const addMsg = (obj, msg, tips) => {
|
|
|
198
228
|
}
|
|
199
229
|
};
|
|
200
230
|
|
|
201
|
-
const setPackage = (pkg, hasUIServer) => {
|
|
231
|
+
const setPackage = (pkg, hasUIServer, hasJs) => {
|
|
202
232
|
const newPkg = type === 'js' ? JS_PKG : TS_PKG;
|
|
203
|
-
const keys = ['scripts', 'devDependencies'];
|
|
233
|
+
const keys = hasJs ? ['scripts', 'devDependencies'] : [];
|
|
204
234
|
if (hasUIServer) {
|
|
205
235
|
keys.push('dependencies', 'tsTypes');
|
|
206
236
|
}
|
|
207
237
|
keys.forEach((key) => {
|
|
208
238
|
const newValue = newPkg[key];
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
const value = pkg[key];
|
|
239
|
+
const k = key === 'tsTypes' ? 'devDependencies' : key;
|
|
240
|
+
const value = pkg[k];
|
|
213
241
|
if (!newValue) {
|
|
214
242
|
return;
|
|
215
243
|
}
|
|
216
244
|
if (!value) {
|
|
217
|
-
pkg[
|
|
245
|
+
pkg[k] = newValue;
|
|
218
246
|
return;
|
|
219
247
|
}
|
|
220
248
|
Object.keys(newValue).forEach((name) => {
|
|
221
|
-
|
|
222
|
-
value[name] = newValue[name];
|
|
223
|
-
}
|
|
249
|
+
value[name] = value[name] || newValue[name];
|
|
224
250
|
});
|
|
225
251
|
});
|
|
226
252
|
};
|
|
227
253
|
|
|
228
|
-
|
|
254
|
+
const trim = (str) => (typeof str === 'string' ? str.trim() : str);
|
|
255
|
+
|
|
256
|
+
const getHooks = (hook) => {
|
|
257
|
+
const result = {};
|
|
258
|
+
const hooks = typeof hook === 'string' ? hook.trim().toLowerCase() : null;
|
|
259
|
+
if (!hooks) {
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
const addItem = (prop, name) => {
|
|
263
|
+
const list = result[prop] || [];
|
|
264
|
+
result[prop] = list;
|
|
265
|
+
if (list.indexOf(name) === -1) {
|
|
266
|
+
list.push(name);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
hooks.split(/[^._a-z]/i).forEach((name) => {
|
|
270
|
+
const h = name.trim();
|
|
271
|
+
if (h === 'empty' || h === 'blank' || h === 'none') {
|
|
272
|
+
result.empty = true;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (h === 'ts' || h === 'typescript') {
|
|
276
|
+
curType = 'ts';
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (h === 'js' || h === 'javascript') {
|
|
280
|
+
curType = 'js';
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (h === 'rules' || h === 'rules.txt') {
|
|
284
|
+
addItem('rulesFiles', 'rules.txt');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (h === '_rules' || h === '_rules.txt' || h === 'reqrules' || h === 'reqrules.txt') {
|
|
288
|
+
addItem('rulesFiles', '_rules.txt');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (h === 'resrules' || h === 'resrules.txt') {
|
|
292
|
+
addItem('rulesFiles', 'resRules.txt');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (h === 'uiserver') {
|
|
297
|
+
result.uiServer = true;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (h === 'auth' || h === 'verify') {
|
|
302
|
+
result.auth = true;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (h === 'snicallback' || h === 'sni') {
|
|
307
|
+
result.sni = true;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (h === 'server') {
|
|
312
|
+
addItem('pipeServers', 'server');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (h === 'rulesserver') {
|
|
317
|
+
addItem('rulesServers', 'rulesServer');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (h === 'tunnelrulesserver') {
|
|
322
|
+
addItem('rulesServers', 'tunnelRulesServer');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (h === 'resrulesserver') {
|
|
327
|
+
addItem('rulesServers', 'resRulesServer');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (h === 'statsserver') {
|
|
331
|
+
addItem('statsServers', 'statsServer');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (h === 'resstatsserver') {
|
|
335
|
+
addItem('statsServers', 'resStatsServer');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const isPipe = h === 'pipe';
|
|
340
|
+
const pipeTunnel = h === 'pipetunnel' || h === 'tunnelpipe';
|
|
341
|
+
const pipeHtttp = h === 'pipehttp' || h === 'httppipe';
|
|
342
|
+
const pipeWs = h === 'pipews' || h === 'wspipe';
|
|
343
|
+
|
|
344
|
+
if (isPipe || pipeTunnel || h === 'pipetunnelreq' || h === 'tunnelreqpipe' || h === 'tunnelreqread' || h === 'tunnelreqwrite') {
|
|
345
|
+
addItem('pipeServers', 'tunnelReqRead');
|
|
346
|
+
addItem('pipeServers', 'tunnelReqWrite');
|
|
347
|
+
}
|
|
348
|
+
if (isPipe || pipeTunnel || h === 'pipetunnelres' || h === 'tunnelrespipe' || h === 'tunnelresread' || h === 'tunnelreswrite') {
|
|
349
|
+
addItem('pipeServers', 'tunnelResRead');
|
|
350
|
+
addItem('pipeServers', 'tunnelResWrite');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (isPipe || pipeHtttp || h === 'pipereq' || h === 'reqpipe' || h === 'reqread' || h === 'reqwrite') {
|
|
354
|
+
addItem('pipeServers', 'reqRead');
|
|
355
|
+
addItem('pipeServers', 'reqWrite');
|
|
356
|
+
}
|
|
357
|
+
if (isPipe || pipeHtttp || h === 'piperes' || h === 'respipe' || h === 'resread' || h === 'reswrite') {
|
|
358
|
+
addItem('pipeServers', 'resRead');
|
|
359
|
+
addItem('pipeServers', 'resWrite');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (isPipe || pipeWs || h === 'pipewsreq' || h === 'wsreqpipe' || h === 'wsreqread' || h === 'wsreqwrite') {
|
|
363
|
+
addItem('pipeServers', 'wsReqRead');
|
|
364
|
+
addItem('pipeServers', 'wsReqWrite');
|
|
365
|
+
}
|
|
366
|
+
if (isPipe || pipeWs || h === 'pipewsres' || h === 'wsrespipe' || h === 'wsresread' || h === 'wsreswrite') {
|
|
367
|
+
addItem('pipeServers', 'wsResRead');
|
|
368
|
+
addItem('pipeServers', 'wsResWrite');
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return result;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
module.exports = async (hooks) => {
|
|
375
|
+
const isBlank = hooks === 'blank' || hooks === 'empty';
|
|
376
|
+
curHooks = isBlank ? {} : getHooks(hooks);
|
|
377
|
+
hasHooks = isBlank || Object.keys(curHooks).length > 0;
|
|
229
378
|
const pkg = getPackage();
|
|
230
379
|
let defaultName;
|
|
380
|
+
if (hasHooks && !curType) {
|
|
381
|
+
curType = fs.existsSync('lib') ? 'js' : 'ts';
|
|
382
|
+
}
|
|
231
383
|
if (/^@[a-z\d_-]+\/whistle\.[a-z\d_-]+$/.test(pkg.name)) {
|
|
232
384
|
defaultName = pkg.name;
|
|
233
385
|
} else if (NAME_RE.test(path.basename(process.cwd()))) {
|
|
234
386
|
defaultName = `${RegExp.$1 || ''}${RegExp.$2 || 'whistle.'}${RegExp.$3}`;
|
|
235
387
|
}
|
|
236
|
-
console.log('\
|
|
237
|
-
const
|
|
388
|
+
console.log('\nFor help see https://github.com/avwo/lack\n'); // eslint-disable-line
|
|
389
|
+
const name = hasHooks ? defaultName : (await inquirer.prompt([
|
|
238
390
|
{
|
|
239
391
|
type: 'input',
|
|
240
392
|
name: 'name',
|
|
241
393
|
message: 'Plugin Name:',
|
|
242
394
|
default: defaultName,
|
|
243
|
-
validate: (input) =>
|
|
244
|
-
return NAME_RE.test(input) || NAME_TIPS;
|
|
245
|
-
},
|
|
395
|
+
validate: (input) => NAME_RE.test(input) || NAME_TIPS,
|
|
246
396
|
},
|
|
247
|
-
]);
|
|
397
|
+
])).name;
|
|
248
398
|
if (!NAME_RE.test(name)) {
|
|
249
399
|
throw new Error(NAME_TIPS);
|
|
250
400
|
}
|
|
251
401
|
pkg.name = `${RegExp.$1 || ''}${RegExp.$2 || 'whistle.'}${RegExp.$3}`;
|
|
252
|
-
|
|
253
|
-
|
|
402
|
+
const defaultVersion = pkg.version || '1.0.0';
|
|
403
|
+
let version;
|
|
404
|
+
let description;
|
|
405
|
+
if (!hasHooks) {
|
|
406
|
+
version = (await inquirer.prompt([
|
|
407
|
+
{
|
|
408
|
+
type: 'input',
|
|
409
|
+
name: 'version',
|
|
410
|
+
message: 'Version:',
|
|
411
|
+
default: defaultVersion,
|
|
412
|
+
},
|
|
413
|
+
])).version;
|
|
414
|
+
description = (await inquirer.prompt([
|
|
415
|
+
{
|
|
416
|
+
type: 'input',
|
|
417
|
+
name: 'description',
|
|
418
|
+
message: 'Description:',
|
|
419
|
+
default: pkg.description || null,
|
|
420
|
+
},
|
|
421
|
+
])).description;
|
|
422
|
+
}
|
|
423
|
+
pkg.version = trim(version) || defaultVersion;
|
|
424
|
+
pkg.description = trim(description) || pkg.description || '';
|
|
425
|
+
pkg.whistleConfig = pkg.whistleConfig || {};
|
|
254
426
|
|
|
255
427
|
const template = await selectTemplate();
|
|
256
428
|
const uiServer = await selectUIServer();
|
|
@@ -260,7 +432,8 @@ module.exports = async () => {
|
|
|
260
432
|
const statsServers = await selectStatsServers();
|
|
261
433
|
const pipeServers = await selectPipeServers();
|
|
262
434
|
const rulesFiles = await selectRulesFiles();
|
|
263
|
-
const msg = [
|
|
435
|
+
const msg = [`Plugin Name: ${pkg.name}`, `\nVersion: ${pkg.version}`,
|
|
436
|
+
`\nDescription: ${pkg.description}`, `\nTemplate: ${template}`];
|
|
264
437
|
if (authFn) {
|
|
265
438
|
msg.push('\nAuth function: Yes');
|
|
266
439
|
}
|
|
@@ -274,19 +447,28 @@ module.exports = async () => {
|
|
|
274
447
|
addMsg(statsServers, msg, 'Stats Servers:');
|
|
275
448
|
addMsg(pipeServers, msg, 'Pipe Servers:');
|
|
276
449
|
addMsg(rulesFiles, msg, 'Rules Files:');
|
|
277
|
-
|
|
450
|
+
const len = msg.length;
|
|
451
|
+
if (len < 4) {
|
|
278
452
|
return;
|
|
279
453
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
name: 'ok',
|
|
285
|
-
message: msg.join('\n'),
|
|
286
|
-
},
|
|
287
|
-
]);
|
|
454
|
+
if (len === 4) {
|
|
455
|
+
msg.pop();
|
|
456
|
+
}
|
|
457
|
+
let ok = hasHooks;
|
|
288
458
|
if (!ok) {
|
|
289
|
-
|
|
459
|
+
msg.push('\nIs this ok?');
|
|
460
|
+
ok = (await inquirer.prompt([
|
|
461
|
+
{
|
|
462
|
+
type: 'confirm',
|
|
463
|
+
name: 'ok',
|
|
464
|
+
message: msg.join('\n'),
|
|
465
|
+
},
|
|
466
|
+
])).ok;
|
|
467
|
+
if (!ok) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
console.log(msg.join('\n')); // eslint-disable-line
|
|
290
472
|
}
|
|
291
473
|
|
|
292
474
|
initReadme(pkg);
|
|
@@ -333,17 +515,18 @@ module.exports = async () => {
|
|
|
333
515
|
if (hasChanged) {
|
|
334
516
|
fs.writeFileSync('index.js', indexFile);
|
|
335
517
|
}
|
|
336
|
-
setPackage(pkg, uiServer);
|
|
518
|
+
setPackage(pkg, uiServer, hasChanged);
|
|
337
519
|
fs.writeFileSync('package.json', JSON.stringify(pkg, null, ' '));
|
|
338
520
|
let showInstall = uiServer;
|
|
339
|
-
if (!isJs) {
|
|
521
|
+
if (!isJs && (hasChanged || !fs.existsSync('assets/ts/src/types'))) {
|
|
340
522
|
showInstall = true;
|
|
341
523
|
copySync('assets/ts/src/types/base.d.ts', 'src/types/base.d.ts');
|
|
342
524
|
copySync('assets/ts/src/types/global.d.ts', 'src/types/global.d.ts');
|
|
343
525
|
copySync('assets/ts/tsconfig.json', 'tsconfig.json');
|
|
344
526
|
}
|
|
345
527
|
if (showInstall) {
|
|
346
|
-
console.log(`\nRun \`npm i\` to install dependencies`); // eslint-disable-line
|
|
528
|
+
console.log(`\nRun \`npm i\` to install dependencies\n`); // eslint-disable-line
|
|
529
|
+
} else {
|
|
530
|
+
console.log(); // eslint-disable-line
|
|
347
531
|
}
|
|
348
|
-
console.log(`${showInstall ? '\n' : '\n\n'}For help see https://github.com/avwo/lack\n\n`); // eslint-disable-line
|
|
349
532
|
};
|
package/bin/watch.js
CHANGED
|
@@ -8,6 +8,7 @@ const HOME_DIR_RE = /^[~~]\//;
|
|
|
8
8
|
const PLUGIN_NAME_RE = /^(?:@[\w-]+\/)?(whistle\.[a-z\d_-]+)$/;
|
|
9
9
|
const REL_RE = /^\.\.[\\/]+/;
|
|
10
10
|
const SLASH_RE = /[\\/]/;
|
|
11
|
+
const BOUNDARY = '\n************************************************\n';
|
|
11
12
|
|
|
12
13
|
const getWhistlePath = () => {
|
|
13
14
|
const dir = process.env.WHISTLE_PATH;
|
|
@@ -17,6 +18,10 @@ const getWhistlePath = () => {
|
|
|
17
18
|
return path.join(os.homedir(), '.WhistleAppData');
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
const showLog = (msg) => {
|
|
22
|
+
console.log(msg); // eslint-disable-line
|
|
23
|
+
};
|
|
24
|
+
|
|
20
25
|
const DEV_PLUGINS = path.join(getWhistlePath(), 'dev_plugins');
|
|
21
26
|
const ROOT = process.cwd();
|
|
22
27
|
const pkgFile = path.join(ROOT, 'package.json');
|
|
@@ -80,13 +85,13 @@ module.exports = (dirs) => {
|
|
|
80
85
|
fs.unlinkSync(logFile); // eslint-disable-line
|
|
81
86
|
} catch (e) {}
|
|
82
87
|
fse.ensureDirSync(DEV_PLUGINS);
|
|
83
|
-
const watchList = ['index.js', 'rules.txt', '_rules
|
|
84
|
-
'
|
|
88
|
+
const watchList = ['index.js', 'rules.txt', '_rules(reqRules).txt', '_values.txt',
|
|
89
|
+
'resRules.txt', 'lib', 'dist', 'public', 'initial(initialize).js'];
|
|
85
90
|
if (dirs && typeof dirs === 'string') {
|
|
86
91
|
dirs.split(',').forEach((dir) => {
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
watchList.push(
|
|
92
|
+
const d = dir.trim();
|
|
93
|
+
if (d && watchList.indexOf(d) === -1) {
|
|
94
|
+
watchList.push(d);
|
|
90
95
|
}
|
|
91
96
|
});
|
|
92
97
|
}
|
|
@@ -104,15 +109,17 @@ module.exports = (dirs) => {
|
|
|
104
109
|
return true;
|
|
105
110
|
}
|
|
106
111
|
}
|
|
112
|
+
return false;
|
|
107
113
|
};
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
showLog(BOUNDARY);
|
|
115
|
+
showLog(`Watching the following files/folders changes:\n${tips}`);
|
|
116
|
+
showLog(BOUNDARY);
|
|
110
117
|
const ignoredRe = /(^|[/\\])(\..|node_modules([/\\]|$))/;
|
|
111
118
|
chokidar.watch(watchList, {
|
|
112
119
|
ignored: ignoredRe,
|
|
113
120
|
}).on('raw', (_, filename, details) => {
|
|
114
|
-
if (filename.includes('package.json') || filename.includes('.console.log')
|
|
115
|
-
ignoredRe.test(filename)) {
|
|
121
|
+
if (filename.includes('package.json') || filename.includes('.console.log')
|
|
122
|
+
|| ignoredRe.test(filename)) {
|
|
116
123
|
return;
|
|
117
124
|
}
|
|
118
125
|
const watchedPath = (details && details.watchedPath) || filename;
|
|
@@ -122,8 +129,7 @@ module.exports = (dirs) => {
|
|
|
122
129
|
}
|
|
123
130
|
clearTimeout(timer);
|
|
124
131
|
timer = setTimeout(() => {
|
|
125
|
-
|
|
126
|
-
console.log(`${hasSlash ? watchedPath : filename} is changed.`); // eslint-disable-line
|
|
132
|
+
showLog(`\n${hasSlash ? watchedPath : filename} is changed.`);
|
|
127
133
|
touch();
|
|
128
134
|
}, 1000);
|
|
129
135
|
}).on('error', () => {});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "whistle extension tools",
|
|
5
5
|
"author": "avenwu <avwu@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,9 +35,8 @@
|
|
|
35
35
|
"lru-cache": "^6.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"eslint": "^
|
|
41
|
-
"eslint-config-imweb": "^0.2.17"
|
|
38
|
+
"eslint": "^8.57.1",
|
|
39
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
40
|
+
"eslint-plugin-import": "^2.32.0"
|
|
42
41
|
}
|
|
43
42
|
}
|