ops-toolkit 1.2.0 → 1.2.1
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/.release-it.json +1 -1
- package/README.md +258 -31
- package/bin/ops-toolkit.ts +4 -87
- package/dist/bin/ops-toolkit.js +11416 -0
- package/dist/index.js +10673 -2983
- package/docs/API.md +850 -0
- package/docs/ARCHITECTURE.md +433 -0
- package/docs/DEVELOPMENT.md +554 -0
- package/package.json +7 -7
- package/src/cli/app.ts +188 -0
- package/src/cli/command-discovery.ts +212 -0
- package/src/cli/command-registry.ts +136 -0
- package/src/commands/monitor/index.ts +199 -58
- package/src/index.ts +4 -77
- package/src/types/ui.ts +3 -3
- package/src/utils/config.ts +385 -64
- package/src/utils/error-handlers.ts +94 -0
- package/src/utils/error-reporter.ts +234 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +418 -22
- package/src/utils/system.ts +26 -3
package/src/utils/config.ts
CHANGED
|
@@ -1,146 +1,467 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
-
import { Logger } from './logger';
|
|
4
|
+
import { Logger, LogLevel } from './logger';
|
|
5
|
+
import { ConfigError } from './error-handlers';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* 配置接口定义
|
|
9
|
+
*/
|
|
10
|
+
export interface OpsConfig {
|
|
11
|
+
version?: string;
|
|
12
|
+
environment?: 'development' | 'production' | 'test';
|
|
13
|
+
monitor: {
|
|
14
|
+
refreshInterval: number;
|
|
15
|
+
showProcesses: boolean;
|
|
16
|
+
maxProcesses: number;
|
|
17
|
+
enableRealTime: boolean;
|
|
18
|
+
};
|
|
19
|
+
logs: {
|
|
20
|
+
defaultPath: string;
|
|
21
|
+
maxLines: number;
|
|
22
|
+
follow: boolean;
|
|
23
|
+
level: LogLevel;
|
|
24
|
+
enableFileLogging: boolean;
|
|
25
|
+
logDirectory: string;
|
|
26
|
+
};
|
|
27
|
+
deploy: {
|
|
28
|
+
defaultEnv: string;
|
|
29
|
+
backupEnabled: boolean;
|
|
30
|
+
confirmBeforeDeploy: boolean;
|
|
31
|
+
rollbackEnabled: boolean;
|
|
32
|
+
maxRetries: number;
|
|
33
|
+
};
|
|
34
|
+
system: {
|
|
35
|
+
showHiddenServices: boolean;
|
|
36
|
+
cacheTimeout: number;
|
|
37
|
+
enableNotifications: boolean;
|
|
38
|
+
};
|
|
39
|
+
ui: {
|
|
40
|
+
theme: string;
|
|
41
|
+
animations: boolean;
|
|
42
|
+
sound: boolean;
|
|
43
|
+
language: string;
|
|
44
|
+
};
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 配置验证器
|
|
50
|
+
*/
|
|
51
|
+
export interface ConfigValidator {
|
|
52
|
+
validate(config: OpsConfig): boolean;
|
|
53
|
+
getErrors(): string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 配置管理器
|
|
58
|
+
*/
|
|
59
|
+
export class ConfigManager {
|
|
8
60
|
private static configDir = path.join(os.homedir(), '.ops-toolkit');
|
|
9
|
-
private static configFile = path.join(
|
|
10
|
-
private static
|
|
61
|
+
private static configFile = path.join(ConfigManager.configDir, 'config.json');
|
|
62
|
+
private static backupDir = path.join(ConfigManager.configDir, 'backups');
|
|
63
|
+
private static config: OpsConfig | null = null;
|
|
64
|
+
private static validators: Map<string, ConfigValidator> = new Map();
|
|
65
|
+
|
|
66
|
+
private static defaultConfig: OpsConfig = {
|
|
67
|
+
environment: 'development',
|
|
68
|
+
version: '1.2.0',
|
|
11
69
|
monitor: {
|
|
12
70
|
refreshInterval: 5000,
|
|
13
71
|
showProcesses: true,
|
|
14
72
|
maxProcesses: 20,
|
|
73
|
+
enableRealTime: false,
|
|
15
74
|
},
|
|
16
75
|
logs: {
|
|
17
76
|
defaultPath: '/var/log',
|
|
18
77
|
maxLines: 1000,
|
|
19
78
|
follow: false,
|
|
79
|
+
level: LogLevel.INFO,
|
|
80
|
+
enableFileLogging: false,
|
|
81
|
+
logDirectory: path.join(os.homedir(), '.ops-toolkit', 'logs'),
|
|
20
82
|
},
|
|
21
83
|
deploy: {
|
|
22
84
|
defaultEnv: 'production',
|
|
23
85
|
backupEnabled: true,
|
|
24
86
|
confirmBeforeDeploy: true,
|
|
87
|
+
rollbackEnabled: true,
|
|
88
|
+
maxRetries: 3,
|
|
25
89
|
},
|
|
26
90
|
system: {
|
|
27
91
|
showHiddenServices: false,
|
|
28
92
|
cacheTimeout: 30000,
|
|
93
|
+
enableNotifications: true,
|
|
29
94
|
},
|
|
30
95
|
ui: {
|
|
31
96
|
theme: 'default',
|
|
32
97
|
animations: true,
|
|
33
98
|
sound: false,
|
|
99
|
+
language: 'zh-CN',
|
|
34
100
|
},
|
|
35
101
|
};
|
|
36
102
|
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
/**
|
|
104
|
+
* 初始化配置系统
|
|
105
|
+
*/
|
|
106
|
+
static async initialize(): Promise<void> {
|
|
39
107
|
try {
|
|
108
|
+
await this.ensureDirectories();
|
|
109
|
+
|
|
40
110
|
if (!fs.existsSync(this.configFile)) {
|
|
41
|
-
this.createDefaultConfig();
|
|
111
|
+
await this.createDefaultConfig();
|
|
42
112
|
}
|
|
43
113
|
|
|
44
|
-
|
|
45
|
-
|
|
114
|
+
await this.loadConfig();
|
|
115
|
+
await this.validateConfig();
|
|
46
116
|
|
|
47
|
-
|
|
48
|
-
|
|
117
|
+
Logger.info('配置系统初始化完成', { configDir: this.configDir });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
120
|
+
throw new ConfigError(`配置系统初始化失败: ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 确保必要的目录存在
|
|
126
|
+
*/
|
|
127
|
+
private static async ensureDirectories(): Promise<void> {
|
|
128
|
+
const directories = [this.configDir, this.backupDir];
|
|
129
|
+
|
|
130
|
+
for (const dir of directories) {
|
|
131
|
+
if (!fs.existsSync(dir)) {
|
|
132
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
133
|
+
Logger.debug(`创建目录: ${dir}`);
|
|
49
134
|
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
50
137
|
|
|
51
|
-
|
|
138
|
+
/**
|
|
139
|
+
* 创建默认配置
|
|
140
|
+
*/
|
|
141
|
+
private static async createDefaultConfig(): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
const configJson = JSON.stringify(this.defaultConfig, null, 2);
|
|
144
|
+
fs.writeFileSync(this.configFile, configJson, 'utf8');
|
|
145
|
+
Logger.success('创建默认配置文件', { configFile: this.configFile });
|
|
52
146
|
} catch (error) {
|
|
53
|
-
|
|
54
|
-
return this.defaultConfig;
|
|
147
|
+
throw new ConfigError(`创建默认配置失败: ${error}`);
|
|
55
148
|
}
|
|
56
149
|
}
|
|
57
150
|
|
|
58
|
-
|
|
59
|
-
|
|
151
|
+
/**
|
|
152
|
+
* 加载配置
|
|
153
|
+
*/
|
|
154
|
+
private static async loadConfig(): Promise<void> {
|
|
60
155
|
try {
|
|
61
|
-
const
|
|
62
|
-
|
|
156
|
+
const configData = fs.readFileSync(this.configFile, 'utf8');
|
|
157
|
+
const loadedConfig = JSON.parse(configData) as OpsConfig;
|
|
158
|
+
|
|
159
|
+
// 合并默认配置和加载的配置
|
|
160
|
+
this.config = this.mergeConfigs(this.defaultConfig, loadedConfig);
|
|
63
161
|
|
|
64
|
-
|
|
65
|
-
|
|
162
|
+
Logger.debug('配置加载完成', {
|
|
163
|
+
environment: this.config.environment,
|
|
164
|
+
version: this.config.version,
|
|
165
|
+
});
|
|
66
166
|
} catch (error) {
|
|
67
|
-
|
|
167
|
+
throw new ConfigError(`加载配置失败: ${error}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 合并配置对象
|
|
173
|
+
*/
|
|
174
|
+
private static mergeConfigs(defaultConfig: OpsConfig, userConfig: OpsConfig): OpsConfig {
|
|
175
|
+
const merged = { ...defaultConfig, ...userConfig };
|
|
176
|
+
|
|
177
|
+
// 深度合并嵌套对象
|
|
178
|
+
for (const key in defaultConfig) {
|
|
179
|
+
if (
|
|
180
|
+
key in userConfig &&
|
|
181
|
+
typeof defaultConfig[key] === 'object' &&
|
|
182
|
+
typeof userConfig[key] === 'object' &&
|
|
183
|
+
!Array.isArray(defaultConfig[key]) &&
|
|
184
|
+
!Array.isArray(userConfig[key])
|
|
185
|
+
) {
|
|
186
|
+
merged[key] = {
|
|
187
|
+
...defaultConfig[key],
|
|
188
|
+
...userConfig[key],
|
|
189
|
+
} as OpsConfig[Extract<keyof OpsConfig, string>];
|
|
190
|
+
}
|
|
68
191
|
}
|
|
192
|
+
|
|
193
|
+
return merged;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 验证配置
|
|
198
|
+
*/
|
|
199
|
+
private static async validateConfig(): Promise<void> {
|
|
200
|
+
if (!this.config) {
|
|
201
|
+
throw new ConfigError('配置未初始化');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let allValid = true;
|
|
205
|
+
const allErrors: string[] = [];
|
|
206
|
+
|
|
207
|
+
// 运行所有验证器
|
|
208
|
+
for (const [name, validator] of this.validators) {
|
|
209
|
+
const isValid = validator.validate(this.config);
|
|
210
|
+
if (!isValid) {
|
|
211
|
+
allValid = false;
|
|
212
|
+
const errors = validator.getErrors();
|
|
213
|
+
allErrors.push(...errors.map(err => `[${name}] ${err}`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!allValid) {
|
|
218
|
+
const errorMessage = `配置验证失败:\n${allErrors.join('\n')}`;
|
|
219
|
+
Logger.error(errorMessage);
|
|
220
|
+
throw new ConfigError(errorMessage);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Logger.debug('配置验证通过');
|
|
69
224
|
}
|
|
70
225
|
|
|
71
|
-
|
|
72
|
-
|
|
226
|
+
/**
|
|
227
|
+
* 注册配置验证器
|
|
228
|
+
*/
|
|
229
|
+
static registerValidator(name: string, validator: ConfigValidator): void {
|
|
230
|
+
this.validators.set(name, validator);
|
|
231
|
+
Logger.debug(`注册配置验证器: ${name}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 获取配置
|
|
236
|
+
*/
|
|
237
|
+
static get<T = OpsConfig>(key?: string): OpsConfig | T {
|
|
238
|
+
if (!this.config) {
|
|
239
|
+
throw new ConfigError('配置未初始化,请先调用 initialize()');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!key) {
|
|
243
|
+
return { ...this.config };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return this.getNestedValue<T>(this.config, key);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 设置配置
|
|
251
|
+
*/
|
|
252
|
+
static async set<T = unknown>(key: string, value: T): Promise<void> {
|
|
253
|
+
if (!this.config) {
|
|
254
|
+
throw new ConfigError('配置未初始化,请先调用 initialize()');
|
|
255
|
+
}
|
|
256
|
+
|
|
73
257
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
258
|
+
// 创建备份
|
|
259
|
+
await this.createBackup();
|
|
260
|
+
|
|
261
|
+
// 设置值
|
|
262
|
+
this.setNestedValue(this.config, key, value);
|
|
263
|
+
|
|
264
|
+
// 保存配置
|
|
265
|
+
await this.saveConfig();
|
|
266
|
+
|
|
267
|
+
// 验证配置
|
|
268
|
+
await this.validateConfig();
|
|
269
|
+
|
|
270
|
+
Logger.success(`配置更新成功: ${key}`, { value });
|
|
76
271
|
} catch (error) {
|
|
77
|
-
|
|
272
|
+
// 回滚配置
|
|
273
|
+
await this.rollbackFromBackup();
|
|
274
|
+
throw new ConfigError(`设置配置失败: ${error}`);
|
|
78
275
|
}
|
|
79
276
|
}
|
|
80
277
|
|
|
81
|
-
|
|
82
|
-
|
|
278
|
+
/**
|
|
279
|
+
* 保存配置
|
|
280
|
+
*/
|
|
281
|
+
private static async saveConfig(): Promise<void> {
|
|
282
|
+
if (!this.config) {
|
|
283
|
+
throw new ConfigError('配置未初始化');
|
|
284
|
+
}
|
|
285
|
+
|
|
83
286
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
287
|
+
const configJson = JSON.stringify(this.config, null, 2);
|
|
288
|
+
fs.writeFileSync(this.configFile, configJson, 'utf8');
|
|
289
|
+
Logger.debug('配置保存完成');
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw new ConfigError(`保存配置失败: ${error}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 创建配置备份
|
|
297
|
+
*/
|
|
298
|
+
private static async createBackup(): Promise<void> {
|
|
299
|
+
if (!fs.existsSync(this.configFile)) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
305
|
+
const backupFile = path.join(this.backupDir, `config-backup-${timestamp}.json`);
|
|
306
|
+
|
|
307
|
+
fs.copyFileSync(this.configFile, backupFile);
|
|
308
|
+
Logger.debug('配置备份创建完成', { backupFile });
|
|
309
|
+
} catch (error) {
|
|
310
|
+
Logger.warning('创建配置备份失败', { error: String(error) });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 从备份回滚
|
|
316
|
+
*/
|
|
317
|
+
private static async rollbackFromBackup(): Promise<void> {
|
|
318
|
+
try {
|
|
319
|
+
const backupFiles = fs
|
|
320
|
+
.readdirSync(this.backupDir)
|
|
321
|
+
.filter(file => file.startsWith('config-backup-'))
|
|
322
|
+
.sort()
|
|
323
|
+
.reverse();
|
|
324
|
+
|
|
325
|
+
if (backupFiles.length > 0) {
|
|
326
|
+
const latestBackup = backupFiles[0];
|
|
327
|
+
if (latestBackup) {
|
|
328
|
+
const backupPath = path.join(this.backupDir, latestBackup);
|
|
329
|
+
|
|
330
|
+
fs.copyFileSync(backupPath, this.configFile);
|
|
331
|
+
await this.loadConfig();
|
|
332
|
+
|
|
333
|
+
Logger.info('配置已回滚到最新备份', { backupFile: latestBackup });
|
|
334
|
+
}
|
|
86
335
|
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
Logger.error('配置回滚失败', { error });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 重置配置
|
|
343
|
+
*/
|
|
344
|
+
static async reset(): Promise<void> {
|
|
345
|
+
try {
|
|
346
|
+
await this.createBackup();
|
|
347
|
+
this.config = { ...this.defaultConfig };
|
|
348
|
+
await this.saveConfig();
|
|
349
|
+
await this.validateConfig();
|
|
87
350
|
|
|
88
|
-
|
|
351
|
+
Logger.success('配置已重置为默认值');
|
|
89
352
|
} catch (error) {
|
|
90
|
-
|
|
353
|
+
throw new ConfigError(`重置配置失败: ${error}`);
|
|
91
354
|
}
|
|
92
355
|
}
|
|
93
356
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
357
|
+
/**
|
|
358
|
+
* 重新加载配置
|
|
359
|
+
*/
|
|
360
|
+
static async reload(): Promise<void> {
|
|
361
|
+
try {
|
|
362
|
+
await this.loadConfig();
|
|
363
|
+
await this.validateConfig();
|
|
364
|
+
Logger.info('配置重新加载完成');
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw new ConfigError(`重新加载配置失败: ${error}`);
|
|
367
|
+
}
|
|
99
368
|
}
|
|
100
369
|
|
|
101
|
-
|
|
102
|
-
|
|
370
|
+
/**
|
|
371
|
+
* 获取嵌套值
|
|
372
|
+
*/
|
|
373
|
+
private static getNestedValue<T = unknown>(obj: OpsConfig, key: string): T {
|
|
103
374
|
const keys = key.split('.');
|
|
104
|
-
|
|
375
|
+
let current: unknown = obj;
|
|
105
376
|
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
current[keyPart]
|
|
377
|
+
for (const keyPart of keys) {
|
|
378
|
+
if (current && typeof current === 'object' && keyPart in current) {
|
|
379
|
+
current = (current as Record<string, unknown>)[keyPart];
|
|
380
|
+
} else {
|
|
381
|
+
return undefined as T;
|
|
109
382
|
}
|
|
110
|
-
|
|
111
|
-
}, obj);
|
|
383
|
+
}
|
|
112
384
|
|
|
113
|
-
|
|
385
|
+
return current as T;
|
|
114
386
|
}
|
|
115
387
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
388
|
+
/**
|
|
389
|
+
* 设置嵌套值
|
|
390
|
+
*/
|
|
391
|
+
private static setNestedValue(obj: OpsConfig, key: string, value: unknown): void {
|
|
392
|
+
const keys = key.split('.');
|
|
393
|
+
const lastKey = keys.pop();
|
|
120
394
|
|
|
121
|
-
|
|
122
|
-
if (!config.monitor || !config.logs || !config.deploy) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
395
|
+
if (!lastKey) return;
|
|
125
396
|
|
|
126
|
-
|
|
127
|
-
if (typeof config.monitor.refreshInterval !== 'number') {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
397
|
+
let current: Record<string, unknown> = obj as Record<string, unknown>;
|
|
130
398
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
399
|
+
for (const keyPart of keys) {
|
|
400
|
+
if (!(keyPart in current) || !current[keyPart] || typeof current[keyPart] !== 'object') {
|
|
401
|
+
current[keyPart] = {};
|
|
402
|
+
}
|
|
403
|
+
current = current[keyPart] as Record<string, unknown>;
|
|
134
404
|
}
|
|
405
|
+
|
|
406
|
+
current[lastKey] = value;
|
|
135
407
|
}
|
|
136
408
|
|
|
137
|
-
|
|
409
|
+
/**
|
|
410
|
+
* 获取配置目录
|
|
411
|
+
*/
|
|
138
412
|
static getConfigDir(): string {
|
|
139
413
|
return this.configDir;
|
|
140
414
|
}
|
|
141
415
|
|
|
142
|
-
|
|
416
|
+
/**
|
|
417
|
+
* 获取配置文件路径
|
|
418
|
+
*/
|
|
143
419
|
static getConfigFile(): string {
|
|
144
420
|
return this.configFile;
|
|
145
421
|
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 获取备份目录
|
|
425
|
+
*/
|
|
426
|
+
static getBackupDir(): string {
|
|
427
|
+
return this.backupDir;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* 清理旧备份
|
|
432
|
+
*/
|
|
433
|
+
static async cleanOldBackups(maxBackups: number = 10): Promise<void> {
|
|
434
|
+
try {
|
|
435
|
+
const backupFiles = fs
|
|
436
|
+
.readdirSync(this.backupDir)
|
|
437
|
+
.filter(file => file.startsWith('config-backup-'))
|
|
438
|
+
.map(file => ({
|
|
439
|
+
file,
|
|
440
|
+
path: path.join(this.backupDir, file),
|
|
441
|
+
mtime: fs.statSync(path.join(this.backupDir, file)).mtime,
|
|
442
|
+
}))
|
|
443
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
444
|
+
|
|
445
|
+
if (backupFiles.length > maxBackups) {
|
|
446
|
+
const filesToDelete = backupFiles.slice(maxBackups);
|
|
447
|
+
|
|
448
|
+
for (const { file, path: filePath } of filesToDelete) {
|
|
449
|
+
if (filePath) {
|
|
450
|
+
fs.unlinkSync(filePath);
|
|
451
|
+
Logger.debug('删除旧备份文件', { file });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
Logger.debug('备份清理完成', {
|
|
457
|
+
totalBackups: backupFiles.length,
|
|
458
|
+
keptBackups: Math.min(maxBackups, backupFiles.length),
|
|
459
|
+
});
|
|
460
|
+
} catch (error) {
|
|
461
|
+
Logger.warning('清理备份文件失败', { error });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
146
464
|
}
|
|
465
|
+
|
|
466
|
+
// 向后兼容的Config类
|
|
467
|
+
export class Config extends ConfigManager {}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ErrorSeverity, errorHandler } from './error-reporter';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 自定义错误类型
|
|
5
|
+
*/
|
|
6
|
+
export class CLIError extends Error {
|
|
7
|
+
public readonly code: string;
|
|
8
|
+
public readonly exitCode: number;
|
|
9
|
+
|
|
10
|
+
constructor(message: string, code: string = 'CLI_ERROR', exitCode: number = 1) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'CLIError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.exitCode = exitCode;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class CommandError extends CLIError {
|
|
19
|
+
constructor(message: string, command: string = '') {
|
|
20
|
+
super(`命令执行失败: ${command} - ${message}`, 'COMMAND_ERROR');
|
|
21
|
+
this.name = 'CommandError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ConfigError extends CLIError {
|
|
26
|
+
constructor(message: string) {
|
|
27
|
+
super(`配置错误: ${message}`, 'CONFIG_ERROR');
|
|
28
|
+
this.name = 'ConfigError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class SystemError extends CLIError {
|
|
33
|
+
constructor(message: string) {
|
|
34
|
+
super(`系统错误: ${message}`, 'SYSTEM_ERROR');
|
|
35
|
+
this.name = 'SystemError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 设置全局错误处理器
|
|
41
|
+
*/
|
|
42
|
+
export function setupErrorHandlers(): void {
|
|
43
|
+
// 未捕获异常处理
|
|
44
|
+
process.on('uncaughtException', (error: Error) => {
|
|
45
|
+
errorHandler(error, { action: 'uncaught_exception' }, ErrorSeverity.CRITICAL);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 未处理的Promise拒绝
|
|
49
|
+
process.on('unhandledRejection', (reason: unknown, promise: Promise<unknown>) => {
|
|
50
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
51
|
+
errorHandler(
|
|
52
|
+
error,
|
|
53
|
+
{
|
|
54
|
+
action: 'unhandled_rejection',
|
|
55
|
+
additionalInfo: { promise: promise.toString() },
|
|
56
|
+
},
|
|
57
|
+
ErrorSeverity.HIGH
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 包装异步函数,提供错误处理
|
|
64
|
+
*/
|
|
65
|
+
export function withErrorHandling<T extends unknown[]>(
|
|
66
|
+
fn: (...args: T) => Promise<void>,
|
|
67
|
+
context?: string
|
|
68
|
+
) {
|
|
69
|
+
return async (...args: T): Promise<void> => {
|
|
70
|
+
try {
|
|
71
|
+
await fn(...args);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
74
|
+
|
|
75
|
+
if (err instanceof CLIError) {
|
|
76
|
+
errorHandler(
|
|
77
|
+
err,
|
|
78
|
+
{
|
|
79
|
+
action: context || 'unknown_action',
|
|
80
|
+
additionalInfo: {
|
|
81
|
+
errorCode: err.code,
|
|
82
|
+
exitCode: err.exitCode,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
err.exitCode === 1 ? ErrorSeverity.HIGH : ErrorSeverity.MEDIUM
|
|
86
|
+
);
|
|
87
|
+
process.exit(err.exitCode);
|
|
88
|
+
} else {
|
|
89
|
+
errorHandler(err, { action: context || 'unknown_action' }, ErrorSeverity.HIGH);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|