@hile/cli 1.0.18 → 1.0.19

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 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 会自动调用 `container.shutdown()`,按逆序销毁已启动服务。
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;
@@ -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,58 @@ 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 pad = (s, n) => s.padEnd(n);
26
+ const padNum = (n, width) => n.toString().padStart(width);
27
+ const tag = colorize ? `${c.dim}${c.cyan}${TAG}${c.reset}` : TAG;
28
+ const target = (s) => (colorize ? `${c.cyan}${s}${c.reset}` : s);
29
+ const ok = (s) => (colorize ? `${c.green}${s}${c.reset}` : s);
30
+ const warn = (s) => (colorize ? `${c.yellow}${s}${c.reset}` : s);
31
+ const err = (s) => (colorize ? `${c.red}${s}${c.reset}` : s);
32
+ const dim = (s) => (colorize ? `${c.dim}${s}${c.reset}` : s);
33
+ const fmt = (t, status, durationMs) => durationMs !== undefined
34
+ ? `${tag} ${target(pad(t, 14))} ${status} ${dim(padNum(durationMs, 5) + 'ms')}`
35
+ : `${tag} ${target(pad(t, 14))} ${status}`;
15
36
  switch (event.type) {
16
37
  case 'service:init':
17
- console.info(`[hile] service#${event.id} init`);
38
+ console.info(fmt(`service#${event.id}`, dim('init')));
18
39
  break;
19
40
  case 'service:ready':
20
- console.info(`[hile] service#${event.id} ready (${event.durationMs}ms)`);
41
+ console.info(fmt(`service#${event.id}`, ok(pad('ready', 14)), event.durationMs));
21
42
  break;
22
43
  case 'service:error':
23
- console.error(`[hile] service#${event.id} failed (${event.durationMs}ms):`, event.error);
44
+ console.error(fmt(`service#${event.id}`, err(`failed (${event.durationMs}ms)`)));
45
+ console.error(event.error);
24
46
  break;
25
47
  case 'service:shutdown:start':
26
- console.info(`[hile] service#${event.id} stopping`);
48
+ console.info(fmt(`service#${event.id}`, warn('stopping')));
27
49
  break;
28
50
  case 'service:shutdown:done':
29
- console.info(`[hile] service#${event.id} stopped (${event.durationMs}ms)`);
51
+ console.info(fmt(`service#${event.id}`, dim(pad('stopped', 14)), event.durationMs));
30
52
  break;
31
53
  case 'service:shutdown:error':
32
- console.error(`[hile] service#${event.id} shutdown error:`, event.error);
54
+ console.error(fmt(`service#${event.id}`, err('shutdown error')));
55
+ console.error(event.error);
33
56
  break;
34
57
  case 'container:shutdown:start':
35
- console.info('[hile] container shutdown start');
58
+ console.info(fmt('container', dim('shutdown start')));
36
59
  break;
37
60
  case 'container:shutdown:done':
38
- console.info(`[hile] container shutdown done (${event.durationMs}ms)`);
61
+ console.info(fmt('container', ok(pad('shutdown done', 14)), event.durationMs));
39
62
  break;
40
63
  case 'container:error':
41
- console.error('[hile] container error:', event.error);
64
+ console.error(fmt('container', err('error')));
65
+ console.error(event.error);
42
66
  break;
43
67
  }
44
68
  }
@@ -106,13 +130,6 @@ program
106
130
  return;
107
131
  }
108
132
  // 注册退出钩子,在进程退出时销毁所有服务
109
- exitHook(exit => {
110
- container.shutdown()
111
- .catch(e => console.error(e))
112
- .finally(() => {
113
- offEvent();
114
- exit();
115
- });
116
- });
133
+ registerExitHook(container, offEvent);
117
134
  });
118
135
  program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@hile/cli",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
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.20",
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": "07067534cbd2e1c4011c72f74d16d18506081aac"
34
+ "gitHead": "f5d59f9f131456cf14e5ca62eeea6becde84ebef"
34
35
  }