@hile/cli 1.0.18 → 1.0.20
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.md +10 -4
- package/dist/exitHook.d.ts +8 -0
- package/dist/exitHook.js +25 -0
- package/dist/index.js +30 -18
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -48,14 +48,14 @@ hile start --env-file .env --env-file .env.local
|
|
|
48
48
|
|
|
49
49
|
### 启动日志(基于 @hile/core 事件)
|
|
50
50
|
|
|
51
|
-
CLI
|
|
51
|
+
CLI 已接入容器事件日志(仅开发模式输出):
|
|
52
52
|
|
|
53
53
|
- 服务启动:`service:init` / `service:ready`(含耗时)
|
|
54
|
-
- 服务失败:`service:error
|
|
54
|
+
- 服务失败:`service:error`(错误对象单独一行)
|
|
55
55
|
- 关闭阶段:`service:shutdown:start` / `service:shutdown:done`
|
|
56
56
|
- 容器关闭:`container:shutdown:start` / `container:shutdown:done`
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
在 TTY 环境下会使用颜色(成功绿、错误红、警告黄等);重定向或管道时自动禁用颜色,便于日志采集。
|
|
59
59
|
|
|
60
60
|
### 其他命令
|
|
61
61
|
|
|
@@ -120,7 +120,12 @@ export default defineService(async (shutdown) => {
|
|
|
120
120
|
|
|
121
121
|
## 优雅关闭
|
|
122
122
|
|
|
123
|
-
进程收到 `SIGTERM`、`SIGINT` 等信号时,CLI
|
|
123
|
+
进程收到 `SIGTERM`、`SIGINT` 等信号时,CLI 通过 `registerExitHook` 注册的退出钩子会:
|
|
124
|
+
|
|
125
|
+
1. 先 **await** `container.shutdown()`(确保所有在 defineService 中注册的 shutdown 回调被执行)
|
|
126
|
+
2. **仅在其完成后**再取消事件订阅并调用 exit 退出进程
|
|
127
|
+
|
|
128
|
+
若 `shutdown()` 未完成,进程会挂起不退出;内部将 async-exit-hook 的强制退出超时设为极大值,等效于「等 shutdown 完成再退出」。
|
|
124
129
|
|
|
125
130
|
## 项目结构示例
|
|
126
131
|
|
|
@@ -144,6 +149,7 @@ my-app/
|
|
|
144
149
|
pnpm install
|
|
145
150
|
pnpm build
|
|
146
151
|
pnpm dev
|
|
152
|
+
pnpm test
|
|
147
153
|
```
|
|
148
154
|
|
|
149
155
|
## License
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface Shutdownable {
|
|
2
|
+
shutdown(): Promise<void>;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* 注册进程退出钩子:进程退出时先执行 container.shutdown(),**仅在其完成后**才执行 offEvent 并 exit。
|
|
6
|
+
* 若 shutdown() 未完成,进程会挂起,不会被关闭(受 FORCE_EXIT_AFTER_MS 上限保护)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerExitHook(container: Shutdownable, offEvent: () => void): void;
|
package/dist/exitHook.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import exitHook from 'async-exit-hook';
|
|
2
|
+
/** 强制退出超时:仅当 shutdown 未在该时间内完成时才强制退出,默认约 24 天,等效于「等 shutdown 完成再退出」 */
|
|
3
|
+
const FORCE_EXIT_AFTER_MS = 2 ** 31 - 1;
|
|
4
|
+
/**
|
|
5
|
+
* 注册进程退出钩子:进程退出时先执行 container.shutdown(),**仅在其完成后**才执行 offEvent 并 exit。
|
|
6
|
+
* 若 shutdown() 未完成,进程会挂起,不会被关闭(受 FORCE_EXIT_AFTER_MS 上限保护)。
|
|
7
|
+
*/
|
|
8
|
+
export function registerExitHook(container, offEvent) {
|
|
9
|
+
const hook = exitHook;
|
|
10
|
+
if (typeof hook.forceExitTimeout === 'function') {
|
|
11
|
+
hook.forceExitTimeout(FORCE_EXIT_AFTER_MS);
|
|
12
|
+
}
|
|
13
|
+
exitHook(async (exit) => {
|
|
14
|
+
try {
|
|
15
|
+
await container.shutdown();
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
console.error(e);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
offEvent();
|
|
22
|
+
exit();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import pkg from '../package.json' with { type: 'json' };
|
|
3
|
-
import exitHook from 'async-exit-hook';
|
|
4
3
|
import { program } from 'commander';
|
|
4
|
+
import { registerExitHook } from './exitHook.js';
|
|
5
5
|
import { glob } from 'glob';
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { container, isService, loadService } from '@hile/core';
|
|
@@ -11,34 +11,53 @@ const require = createRequire(import.meta.url);
|
|
|
11
11
|
function loadEnvFile(filePath) {
|
|
12
12
|
process.loadEnvFile(resolve(process.cwd(), filePath));
|
|
13
13
|
}
|
|
14
|
+
const TAG = '[hile]';
|
|
15
|
+
const c = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
cyan: '\x1b[36m',
|
|
22
|
+
};
|
|
23
|
+
const colorize = process.stdout.isTTY;
|
|
14
24
|
function logContainerEvent(event) {
|
|
25
|
+
const tag = colorize ? `${c.dim}${c.cyan}${TAG}${c.reset}` : TAG;
|
|
26
|
+
const target = (s) => (colorize ? `${c.cyan}${s}${c.reset}` : s);
|
|
27
|
+
const ok = (s) => (colorize ? `${c.green}${s}${c.reset}` : s);
|
|
28
|
+
const warn = (s) => (colorize ? `${c.yellow}${s}${c.reset}` : s);
|
|
29
|
+
const err = (s) => (colorize ? `${c.red}${s}${c.reset}` : s);
|
|
30
|
+
const dim = (s) => (colorize ? `${c.dim}${s}${c.reset}` : s);
|
|
15
31
|
switch (event.type) {
|
|
16
32
|
case 'service:init':
|
|
17
|
-
console.info(`
|
|
33
|
+
console.info(`${tag} ${target(`service#${event.id}`)} ${dim('init')}`);
|
|
18
34
|
break;
|
|
19
35
|
case 'service:ready':
|
|
20
|
-
console.info(`
|
|
36
|
+
console.info(`${tag} ${target(`service#${event.id}`)} ${ok('ready')} ${dim(`(${event.durationMs}ms)`)}`);
|
|
21
37
|
break;
|
|
22
38
|
case 'service:error':
|
|
23
|
-
console.error(`
|
|
39
|
+
console.error(`${tag} ${target(`service#${event.id}`)} ${err('failed')} ${dim(`(${event.durationMs}ms)`)}`);
|
|
40
|
+
console.error(event.error);
|
|
24
41
|
break;
|
|
25
42
|
case 'service:shutdown:start':
|
|
26
|
-
console.info(`
|
|
43
|
+
console.info(`${tag} ${target(`service#${event.id}`)} ${warn('stopping')}`);
|
|
27
44
|
break;
|
|
28
45
|
case 'service:shutdown:done':
|
|
29
|
-
console.info(`
|
|
46
|
+
console.info(`${tag} ${target(`service#${event.id}`)} ${dim('stopped')} ${dim(`(${event.durationMs}ms)`)}`);
|
|
30
47
|
break;
|
|
31
48
|
case 'service:shutdown:error':
|
|
32
|
-
console.error(`
|
|
49
|
+
console.error(`${tag} ${target(`service#${event.id}`)} ${err('shutdown error')}`);
|
|
50
|
+
console.error(event.error);
|
|
33
51
|
break;
|
|
34
52
|
case 'container:shutdown:start':
|
|
35
|
-
console.info('
|
|
53
|
+
console.info(`${tag} ${target('container')} ${dim('shutdown start')}`);
|
|
36
54
|
break;
|
|
37
55
|
case 'container:shutdown:done':
|
|
38
|
-
console.info(
|
|
56
|
+
console.info(`${tag} ${target('container')} ${ok('shutdown done')} ${dim(`(${event.durationMs}ms)`)}`);
|
|
39
57
|
break;
|
|
40
58
|
case 'container:error':
|
|
41
|
-
console.error('
|
|
59
|
+
console.error(`${tag} ${target('container')} ${err('error')}`);
|
|
60
|
+
console.error(event.error);
|
|
42
61
|
break;
|
|
43
62
|
}
|
|
44
63
|
}
|
|
@@ -106,13 +125,6 @@ program
|
|
|
106
125
|
return;
|
|
107
126
|
}
|
|
108
127
|
// 注册退出钩子,在进程退出时销毁所有服务
|
|
109
|
-
|
|
110
|
-
container.shutdown()
|
|
111
|
-
.catch(e => console.error(e))
|
|
112
|
-
.finally(() => {
|
|
113
|
-
offEvent();
|
|
114
|
-
exit();
|
|
115
|
-
});
|
|
116
|
-
});
|
|
128
|
+
registerExitHook(container, offEvent);
|
|
117
129
|
});
|
|
118
130
|
program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc -b && fix-esm-import-path --preserve-import-type ./dist",
|
|
8
|
-
"dev": "tsc -b --watch"
|
|
8
|
+
"dev": "tsc -b --watch",
|
|
9
|
+
"test": "vitest run"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|
|
@@ -24,11 +25,11 @@
|
|
|
24
25
|
"vitest": "^4.0.18"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@hile/core": "^1.0.
|
|
28
|
+
"@hile/core": "^1.0.21",
|
|
28
29
|
"async-exit-hook": "^2.0.1",
|
|
29
30
|
"commander": "^14.0.3",
|
|
30
31
|
"glob": "^13.0.6",
|
|
31
32
|
"tsx": "^4.21.0"
|
|
32
33
|
},
|
|
33
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "a913724465de986a7891746ced001ff6dcd3ff88"
|
|
34
35
|
}
|