flashclaw 1.0.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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/config/plugins.json +23 -0
  4. package/dist/agent-runner.d.ts +103 -0
  5. package/dist/agent-runner.d.ts.map +1 -0
  6. package/dist/agent-runner.js +530 -0
  7. package/dist/agent-runner.js.map +1 -0
  8. package/dist/cli.d.ts +7 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +497 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands.d.ts +68 -0
  13. package/dist/commands.d.ts.map +1 -0
  14. package/dist/commands.js +252 -0
  15. package/dist/commands.js.map +1 -0
  16. package/dist/config-schema.d.ts +21 -0
  17. package/dist/config-schema.d.ts.map +1 -0
  18. package/dist/config-schema.js +26 -0
  19. package/dist/config-schema.js.map +1 -0
  20. package/dist/config.d.ts +11 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +36 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/core/api-client.d.ts +236 -0
  25. package/dist/core/api-client.d.ts.map +1 -0
  26. package/dist/core/api-client.js +369 -0
  27. package/dist/core/api-client.js.map +1 -0
  28. package/dist/core/memory.d.ts +291 -0
  29. package/dist/core/memory.d.ts.map +1 -0
  30. package/dist/core/memory.js +754 -0
  31. package/dist/core/memory.js.map +1 -0
  32. package/dist/core/model-capabilities.d.ts +45 -0
  33. package/dist/core/model-capabilities.d.ts.map +1 -0
  34. package/dist/core/model-capabilities.js +85 -0
  35. package/dist/core/model-capabilities.js.map +1 -0
  36. package/dist/db.d.ts +103 -0
  37. package/dist/db.d.ts.map +1 -0
  38. package/dist/db.js +380 -0
  39. package/dist/db.js.map +1 -0
  40. package/dist/errors.d.ts +22 -0
  41. package/dist/errors.d.ts.map +1 -0
  42. package/dist/errors.js +44 -0
  43. package/dist/errors.js.map +1 -0
  44. package/dist/health.d.ts +27 -0
  45. package/dist/health.d.ts.map +1 -0
  46. package/dist/health.js +55 -0
  47. package/dist/health.js.map +1 -0
  48. package/dist/index.d.ts +11 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +1181 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/logger.d.ts +9 -0
  53. package/dist/logger.d.ts.map +1 -0
  54. package/dist/logger.js +19 -0
  55. package/dist/logger.js.map +1 -0
  56. package/dist/message-queue.d.ts +69 -0
  57. package/dist/message-queue.d.ts.map +1 -0
  58. package/dist/message-queue.js +198 -0
  59. package/dist/message-queue.js.map +1 -0
  60. package/dist/metrics.d.ts +46 -0
  61. package/dist/metrics.d.ts.map +1 -0
  62. package/dist/metrics.js +101 -0
  63. package/dist/metrics.js.map +1 -0
  64. package/dist/paths.d.ts +81 -0
  65. package/dist/paths.d.ts.map +1 -0
  66. package/dist/paths.js +127 -0
  67. package/dist/paths.js.map +1 -0
  68. package/dist/plugins/index.d.ts +9 -0
  69. package/dist/plugins/index.d.ts.map +1 -0
  70. package/dist/plugins/index.js +13 -0
  71. package/dist/plugins/index.js.map +1 -0
  72. package/dist/plugins/installer.d.ts +120 -0
  73. package/dist/plugins/installer.d.ts.map +1 -0
  74. package/dist/plugins/installer.js +1008 -0
  75. package/dist/plugins/installer.js.map +1 -0
  76. package/dist/plugins/loader.d.ts +37 -0
  77. package/dist/plugins/loader.d.ts.map +1 -0
  78. package/dist/plugins/loader.js +429 -0
  79. package/dist/plugins/loader.js.map +1 -0
  80. package/dist/plugins/manager.d.ts +72 -0
  81. package/dist/plugins/manager.d.ts.map +1 -0
  82. package/dist/plugins/manager.js +187 -0
  83. package/dist/plugins/manager.js.map +1 -0
  84. package/dist/plugins/types.d.ts +101 -0
  85. package/dist/plugins/types.d.ts.map +1 -0
  86. package/dist/plugins/types.js +12 -0
  87. package/dist/plugins/types.js.map +1 -0
  88. package/dist/session-tracker.d.ts +81 -0
  89. package/dist/session-tracker.d.ts.map +1 -0
  90. package/dist/session-tracker.js +228 -0
  91. package/dist/session-tracker.js.map +1 -0
  92. package/dist/task-scheduler.d.ts +47 -0
  93. package/dist/task-scheduler.d.ts.map +1 -0
  94. package/dist/task-scheduler.js +331 -0
  95. package/dist/task-scheduler.js.map +1 -0
  96. package/dist/types.d.ts +57 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +2 -0
  99. package/dist/types.js.map +1 -0
  100. package/dist/utils/env-substitute.d.ts +63 -0
  101. package/dist/utils/env-substitute.d.ts.map +1 -0
  102. package/dist/utils/env-substitute.js +133 -0
  103. package/dist/utils/env-substitute.js.map +1 -0
  104. package/dist/utils/log-rotate.d.ts +19 -0
  105. package/dist/utils/log-rotate.d.ts.map +1 -0
  106. package/dist/utils/log-rotate.js +85 -0
  107. package/dist/utils/log-rotate.js.map +1 -0
  108. package/dist/utils/rate-limiter.d.ts +38 -0
  109. package/dist/utils/rate-limiter.d.ts.map +1 -0
  110. package/dist/utils/rate-limiter.js +79 -0
  111. package/dist/utils/rate-limiter.js.map +1 -0
  112. package/dist/utils/retry.d.ts +10 -0
  113. package/dist/utils/retry.d.ts.map +1 -0
  114. package/dist/utils/retry.js +47 -0
  115. package/dist/utils/retry.js.map +1 -0
  116. package/dist/utils.d.ts +86 -0
  117. package/dist/utils.d.ts.map +1 -0
  118. package/dist/utils.js +218 -0
  119. package/dist/utils.js.map +1 -0
  120. package/package.json +78 -0
  121. package/plugins/cancel-task/index.ts +161 -0
  122. package/plugins/cancel-task/plugin.json +9 -0
  123. package/plugins/feishu/index.ts +944 -0
  124. package/plugins/feishu/plugin.json +29 -0
  125. package/plugins/list-tasks/index.ts +150 -0
  126. package/plugins/list-tasks/plugin.json +9 -0
  127. package/plugins/memory/index.ts +190 -0
  128. package/plugins/memory/plugin.json +7 -0
  129. package/plugins/pause-task/index.ts +95 -0
  130. package/plugins/pause-task/plugin.json +8 -0
  131. package/plugins/register-group/index.ts +147 -0
  132. package/plugins/register-group/plugin.json +7 -0
  133. package/plugins/resume-task/index.ts +92 -0
  134. package/plugins/resume-task/plugin.json +8 -0
  135. package/plugins/schedule-task/index.ts +248 -0
  136. package/plugins/schedule-task/plugin.json +9 -0
  137. package/plugins/send-message/index.ts +75 -0
  138. package/plugins/send-message/plugin.json +9 -0
@@ -0,0 +1,19 @@
1
+ interface RotateOptions {
2
+ maxSize?: number;
3
+ maxFiles?: number;
4
+ compress?: boolean;
5
+ }
6
+ /**
7
+ * 检查并执行日志轮转
8
+ */
9
+ export declare function rotateLogIfNeeded(logPath: string, options?: RotateOptions): void;
10
+ /**
11
+ * 清理旧日志文件
12
+ */
13
+ export declare function cleanOldLogs(logDir: string, maxAgeDays?: number): void;
14
+ /**
15
+ * 获取日志目录大小
16
+ */
17
+ export declare function getLogDirSize(logDir: string): number;
18
+ export {};
19
+ //# sourceMappingURL=log-rotate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-rotate.d.ts","sourceRoot":"","sources":["../../src/utils/log-rotate.ts"],"names":[],"mappings":"AAUA,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAQD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI,CAuCpF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,IAAI,CAqBzE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAepD"}
@@ -0,0 +1,85 @@
1
+ // src/utils/log-rotate.ts
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import pino from 'pino';
5
+ const logger = pino({
6
+ level: 'info',
7
+ transport: { target: 'pino-pretty', options: { colorize: true } }
8
+ });
9
+ const defaultOptions = {
10
+ maxSize: 10 * 1024 * 1024, // 10MB
11
+ maxFiles: 5,
12
+ compress: false,
13
+ };
14
+ /**
15
+ * 检查并执行日志轮转
16
+ */
17
+ export function rotateLogIfNeeded(logPath, options = {}) {
18
+ const opts = { ...defaultOptions, ...options };
19
+ if (!fs.existsSync(logPath)) {
20
+ return;
21
+ }
22
+ const stats = fs.statSync(logPath);
23
+ if (stats.size < opts.maxSize) {
24
+ return;
25
+ }
26
+ logger.info({ logPath, size: stats.size }, '⚡ 执行日志轮转...');
27
+ const dir = path.dirname(logPath);
28
+ const ext = path.extname(logPath);
29
+ const base = path.basename(logPath, ext);
30
+ // 删除最旧的日志
31
+ for (let i = opts.maxFiles - 1; i >= 1; i--) {
32
+ const oldPath = path.join(dir, `${base}.${i}${ext}`);
33
+ const newPath = path.join(dir, `${base}.${i + 1}${ext}`);
34
+ if (i === opts.maxFiles - 1 && fs.existsSync(oldPath)) {
35
+ fs.unlinkSync(oldPath);
36
+ }
37
+ else if (fs.existsSync(oldPath)) {
38
+ fs.renameSync(oldPath, newPath);
39
+ }
40
+ }
41
+ // 重命名当前日志
42
+ const rotatedPath = path.join(dir, `${base}.1${ext}`);
43
+ fs.renameSync(logPath, rotatedPath);
44
+ // 创建新的空日志文件
45
+ fs.writeFileSync(logPath, '');
46
+ logger.info({ rotatedTo: rotatedPath }, '⚡ 日志轮转完成');
47
+ }
48
+ /**
49
+ * 清理旧日志文件
50
+ */
51
+ export function cleanOldLogs(logDir, maxAgeDays = 7) {
52
+ if (!fs.existsSync(logDir)) {
53
+ return;
54
+ }
55
+ const now = Date.now();
56
+ const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
57
+ const files = fs.readdirSync(logDir);
58
+ for (const file of files) {
59
+ if (!file.endsWith('.log'))
60
+ continue;
61
+ const filePath = path.join(logDir, file);
62
+ const stats = fs.statSync(filePath);
63
+ if (now - stats.mtimeMs > maxAge) {
64
+ fs.unlinkSync(filePath);
65
+ logger.info({ file }, '⚡ 删除过期日志');
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * 获取日志目录大小
71
+ */
72
+ export function getLogDirSize(logDir) {
73
+ if (!fs.existsSync(logDir)) {
74
+ return 0;
75
+ }
76
+ let totalSize = 0;
77
+ const files = fs.readdirSync(logDir);
78
+ for (const file of files) {
79
+ const filePath = path.join(logDir, file);
80
+ const stats = fs.statSync(filePath);
81
+ totalSize += stats.size;
82
+ }
83
+ return totalSize;
84
+ }
85
+ //# sourceMappingURL=log-rotate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-rotate.js","sourceRoot":"","sources":["../../src/utils/log-rotate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 令牌桶速率限制器
3
+ */
4
+ export declare class RateLimiter {
5
+ private tokens;
6
+ private lastRefill;
7
+ private readonly maxTokens;
8
+ private readonly refillRate;
9
+ constructor(options?: {
10
+ maxTokens?: number;
11
+ refillRate?: number;
12
+ });
13
+ private refill;
14
+ /**
15
+ * 尝试获取令牌
16
+ * @returns true 如果获取成功,false 如果被限流
17
+ */
18
+ tryAcquire(tokens?: number): boolean;
19
+ /**
20
+ * 等待直到获取令牌
21
+ */
22
+ acquire(tokens?: number): Promise<void>;
23
+ /**
24
+ * 获取当前可用令牌数
25
+ */
26
+ getAvailableTokens(): number;
27
+ /**
28
+ * 获取等待时间(毫秒)
29
+ */
30
+ getWaitTime(tokens?: number): number;
31
+ }
32
+ export declare const apiRateLimiter: RateLimiter;
33
+ export declare const messageRateLimiter: RateLimiter;
34
+ /**
35
+ * 带限流的函数包装器
36
+ */
37
+ export declare function withRateLimit<T>(limiter: RateLimiter, fn: () => Promise<T>, tokens?: number): Promise<T>;
38
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,GAAE;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KAChB;IAON,OAAO,CAAC,MAAM;IASd;;;OAGG;IACH,UAAU,CAAC,MAAM,GAAE,MAAU,GAAG,OAAO;IAWvC;;OAEG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAK5B;;OAEG;IACH,WAAW,CAAC,MAAM,GAAE,MAAU,GAAG,MAAM;CASxC;AAGD,eAAO,MAAM,cAAc,aAGzB,CAAC;AAGH,eAAO,MAAM,kBAAkB,aAG7B,CAAC;AAEH;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,OAAO,EAAE,WAAW,EACpB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,GAAE,MAAU,GACjB,OAAO,CAAC,CAAC,CAAC,CAGZ"}
@@ -0,0 +1,79 @@
1
+ // src/utils/rate-limiter.ts
2
+ /**
3
+ * 令牌桶速率限制器
4
+ */
5
+ export class RateLimiter {
6
+ tokens;
7
+ lastRefill;
8
+ maxTokens;
9
+ refillRate; // 每秒补充的令牌数
10
+ constructor(options = {}) {
11
+ this.maxTokens = options.maxTokens ?? 60;
12
+ this.refillRate = options.refillRate ?? 1;
13
+ this.tokens = this.maxTokens;
14
+ this.lastRefill = Date.now();
15
+ }
16
+ refill() {
17
+ const now = Date.now();
18
+ const elapsed = (now - this.lastRefill) / 1000;
19
+ const tokensToAdd = elapsed * this.refillRate;
20
+ this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
21
+ this.lastRefill = now;
22
+ }
23
+ /**
24
+ * 尝试获取令牌
25
+ * @returns true 如果获取成功,false 如果被限流
26
+ */
27
+ tryAcquire(tokens = 1) {
28
+ this.refill();
29
+ if (this.tokens >= tokens) {
30
+ this.tokens -= tokens;
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ /**
36
+ * 等待直到获取令牌
37
+ */
38
+ async acquire(tokens = 1) {
39
+ while (!this.tryAcquire(tokens)) {
40
+ const waitTime = Math.ceil((tokens - this.tokens) / this.refillRate * 1000);
41
+ await new Promise(resolve => setTimeout(resolve, Math.min(waitTime, 1000)));
42
+ }
43
+ }
44
+ /**
45
+ * 获取当前可用令牌数
46
+ */
47
+ getAvailableTokens() {
48
+ this.refill();
49
+ return Math.floor(this.tokens);
50
+ }
51
+ /**
52
+ * 获取等待时间(毫秒)
53
+ */
54
+ getWaitTime(tokens = 1) {
55
+ this.refill();
56
+ if (this.tokens >= tokens) {
57
+ return 0;
58
+ }
59
+ return Math.ceil((tokens - this.tokens) / this.refillRate * 1000);
60
+ }
61
+ }
62
+ // 全局限流器实例(用于 API 调用)
63
+ export const apiRateLimiter = new RateLimiter({
64
+ maxTokens: 60, // 每分钟最多 60 次
65
+ refillRate: 1, // 每秒补充 1 个令牌
66
+ });
67
+ // 用于消息发送的限流器
68
+ export const messageRateLimiter = new RateLimiter({
69
+ maxTokens: 30, // 每分钟最多 30 条消息
70
+ refillRate: 0.5, // 每 2 秒补充 1 个令牌
71
+ });
72
+ /**
73
+ * 带限流的函数包装器
74
+ */
75
+ export async function withRateLimit(limiter, fn, tokens = 1) {
76
+ await limiter.acquire(tokens);
77
+ return fn();
78
+ }
79
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface RetryOptions {
2
+ maxRetries?: number;
3
+ initialDelay?: number;
4
+ maxDelay?: number;
5
+ factor?: number;
6
+ retryCondition?: (error: Error) => boolean;
7
+ }
8
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
9
+ export declare function isRetryableError(error: Error): boolean;
10
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC5C;AAUD,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAWtD"}
@@ -0,0 +1,47 @@
1
+ // src/utils/retry.ts
2
+ import pino from 'pino';
3
+ const logger = pino({
4
+ level: process.env.LOG_LEVEL || 'info',
5
+ transport: { target: 'pino-pretty', options: { colorize: true } }
6
+ });
7
+ const defaultOptions = {
8
+ maxRetries: 3,
9
+ initialDelay: 1000,
10
+ maxDelay: 30000,
11
+ factor: 2,
12
+ retryCondition: () => true,
13
+ };
14
+ export async function withRetry(fn, options = {}) {
15
+ const opts = { ...defaultOptions, ...options };
16
+ let lastError;
17
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
18
+ try {
19
+ return await fn();
20
+ }
21
+ catch (error) {
22
+ lastError = error;
23
+ if (attempt === opts.maxRetries || !opts.retryCondition(lastError)) {
24
+ throw lastError;
25
+ }
26
+ const delay = Math.min(opts.initialDelay * Math.pow(opts.factor, attempt), opts.maxDelay);
27
+ logger.warn({ attempt: attempt + 1, delay, error: lastError.message }, '重试中...');
28
+ await sleep(delay);
29
+ }
30
+ }
31
+ throw lastError;
32
+ }
33
+ function sleep(ms) {
34
+ return new Promise(resolve => setTimeout(resolve, ms));
35
+ }
36
+ // 判断是否是可重试的错误
37
+ export function isRetryableError(error) {
38
+ const message = error.message.toLowerCase();
39
+ return (message.includes('timeout') ||
40
+ message.includes('econnreset') ||
41
+ message.includes('econnrefused') ||
42
+ message.includes('rate limit') ||
43
+ message.includes('429') ||
44
+ message.includes('503') ||
45
+ message.includes('502'));
46
+ }
47
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,SAAS,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;CAClE,CAAC,CAAC;AAUH,MAAM,cAAc,GAA2B;IAC7C,UAAU,EAAE,CAAC;IACb,YAAY,EAAE,IAAI;IAClB,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,CAAC;IACT,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;CAC3B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,UAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAC;IAC/C,IAAI,SAAgB,CAAC;IAErB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC5D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAc,CAAC;YAE3B,IAAI,OAAO,KAAK,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnE,MAAM,SAAS,CAAC;YAClB,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,IAAI,CAAC,QAAQ,CACd,CAAC;YAEF,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;YACjF,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,MAAM,SAAU,CAAC;AACnB,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,cAAc;AACd,MAAM,UAAU,gBAAgB,CAAC,KAAY;IAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,CACL,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 配置备份选项
3
+ */
4
+ export interface BackupOptions {
5
+ /** 最大备份数量,默认 5 */
6
+ maxBackups?: number;
7
+ /** 是否启用备份,默认 true */
8
+ enabled?: boolean;
9
+ }
10
+ /**
11
+ * 获取备份文件列表(按编号排序)
12
+ * 返回格式:[{ path: string, number: number }, ...]
13
+ */
14
+ export declare function getBackupFiles(filePath: string): Array<{
15
+ path: string;
16
+ number: number;
17
+ }>;
18
+ /**
19
+ * 轮转备份文件
20
+ * 将现有备份编号+1,删除超出限制的旧备份
21
+ */
22
+ export declare function rotateBackups(filePath: string, maxBackups: number): void;
23
+ /**
24
+ * 创建配置文件备份
25
+ *
26
+ * @param filePath 原配置文件路径
27
+ * @param options 备份选项
28
+ * @returns 是否备份成功
29
+ */
30
+ export declare function backupConfig(filePath: string, options?: BackupOptions): boolean;
31
+ /**
32
+ * 列出可用的备份
33
+ *
34
+ * @param filePath 原配置文件路径
35
+ * @returns 备份信息列表
36
+ */
37
+ export declare function listBackups(filePath: string): Array<{
38
+ path: string;
39
+ number: number;
40
+ modifiedAt: Date;
41
+ size: number;
42
+ }>;
43
+ /**
44
+ * 从备份恢复配置
45
+ *
46
+ * @param filePath 原配置文件路径
47
+ * @param backupNumber 备份编号(1-5),默认恢复最新的备份(编号 1)
48
+ * @returns 是否恢复成功
49
+ */
50
+ export declare function restoreConfig(filePath: string, backupNumber?: number): boolean;
51
+ /**
52
+ * 加载 JSON 文件
53
+ *
54
+ * @param filePath - JSON 文件路径
55
+ * @param defaultValue - 文件不存在或解析失败时的默认值
56
+ * @returns 解析后的 JSON 对象
57
+ */
58
+ export declare function loadJson<T>(filePath: string, defaultValue: T): T;
59
+ /**
60
+ * 加载 JSON 文件并替换环境变量
61
+ *
62
+ * 支持的环境变量语法:
63
+ * - ${VAR} - 从环境变量获取值
64
+ * - ${VAR:-default} - 有默认值的环境变量
65
+ *
66
+ * @param filePath - JSON 文件路径
67
+ * @param defaultValue - 文件不存在或解析失败时的默认值
68
+ * @param env - 环境变量对象 (默认使用 process.env)
69
+ * @returns 替换环境变量后的 JSON 对象
70
+ *
71
+ * @example
72
+ * // config.json: { "apiUrl": "${API_URL:-http://localhost:3000}" }
73
+ * loadJsonWithEnv('config.json', {})
74
+ * // 返回: { "apiUrl": "http://localhost:3000" } (如果 API_URL 未定义)
75
+ */
76
+ export declare function loadJsonWithEnv<T>(filePath: string, defaultValue: T, env?: Record<string, string | undefined>): T;
77
+ /**
78
+ * 保存 JSON 配置文件
79
+ * 写入前自动创建备份,备份失败不阻塞写入
80
+ *
81
+ * @param filePath 文件路径
82
+ * @param data 要保存的数据
83
+ * @param backupOptions 备份选项
84
+ */
85
+ export declare function saveJson(filePath: string, data: unknown, backupOptions?: BackupOptions): void;
86
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAcD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBxF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CA+BxE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CA0B/E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAmBD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,OAAO,CAwBzE;AAMD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAShE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,CAAC,EACf,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,CAAC,CAGH;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAO7F"}
package/dist/utils.js ADDED
@@ -0,0 +1,218 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { substituteEnvVarsDeep } from './utils/env-substitute.js';
4
+ import { createLogger } from './logger.js';
5
+ const logger = createLogger('ConfigBackup');
6
+ const DEFAULT_BACKUP_OPTIONS = {
7
+ maxBackups: 5,
8
+ enabled: true,
9
+ };
10
+ /**
11
+ * 转义正则表达式特殊字符
12
+ */
13
+ function escapeRegExp(str) {
14
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ }
16
+ /**
17
+ * 获取备份文件列表(按编号排序)
18
+ * 返回格式:[{ path: string, number: number }, ...]
19
+ */
20
+ export function getBackupFiles(filePath) {
21
+ const dir = path.dirname(filePath);
22
+ const baseName = path.basename(filePath);
23
+ if (!fs.existsSync(dir)) {
24
+ return [];
25
+ }
26
+ const files = fs.readdirSync(dir);
27
+ const backupPattern = new RegExp(`^${escapeRegExp(baseName)}\\.bak\\.(\\d+)$`);
28
+ const backups = [];
29
+ for (const file of files) {
30
+ const match = file.match(backupPattern);
31
+ if (match) {
32
+ backups.push({
33
+ path: path.join(dir, file),
34
+ number: parseInt(match[1], 10),
35
+ });
36
+ }
37
+ }
38
+ // 按编号升序排序
39
+ return backups.sort((a, b) => a.number - b.number);
40
+ }
41
+ /**
42
+ * 轮转备份文件
43
+ * 将现有备份编号+1,删除超出限制的旧备份
44
+ */
45
+ export function rotateBackups(filePath, maxBackups) {
46
+ const backups = getBackupFiles(filePath);
47
+ if (backups.length === 0) {
48
+ return;
49
+ }
50
+ // 按编号降序处理(从大到小重命名,避免覆盖)
51
+ const sortedDesc = [...backups].sort((a, b) => b.number - a.number);
52
+ for (const backup of sortedDesc) {
53
+ const newNumber = backup.number + 1;
54
+ if (newNumber > maxBackups) {
55
+ // 超出限制,删除
56
+ try {
57
+ fs.unlinkSync(backup.path);
58
+ logger.debug({ path: backup.path }, '删除旧备份');
59
+ }
60
+ catch (err) {
61
+ logger.warn({ path: backup.path, err }, '删除旧备份失败');
62
+ }
63
+ }
64
+ else {
65
+ // 重命名为新编号
66
+ const newPath = backup.path.replace(/\.bak\.\d+$/, `.bak.${newNumber}`);
67
+ try {
68
+ fs.renameSync(backup.path, newPath);
69
+ }
70
+ catch (err) {
71
+ logger.warn({ from: backup.path, to: newPath, err }, '重命名备份失败');
72
+ }
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * 创建配置文件备份
78
+ *
79
+ * @param filePath 原配置文件路径
80
+ * @param options 备份选项
81
+ * @returns 是否备份成功
82
+ */
83
+ export function backupConfig(filePath, options) {
84
+ const opts = { ...DEFAULT_BACKUP_OPTIONS, ...options };
85
+ if (!opts.enabled) {
86
+ return true;
87
+ }
88
+ // 如果原文件不存在,无需备份
89
+ if (!fs.existsSync(filePath)) {
90
+ return true;
91
+ }
92
+ try {
93
+ // 先轮转现有备份
94
+ rotateBackups(filePath, opts.maxBackups);
95
+ // 创建新备份(编号为 1)
96
+ const backupPath = `${filePath}.bak.1`;
97
+ fs.copyFileSync(filePath, backupPath);
98
+ logger.debug({ original: filePath, backup: backupPath }, '配置已备份');
99
+ return true;
100
+ }
101
+ catch (err) {
102
+ logger.warn({ filePath, err }, '配置备份失败');
103
+ return false;
104
+ }
105
+ }
106
+ /**
107
+ * 列出可用的备份
108
+ *
109
+ * @param filePath 原配置文件路径
110
+ * @returns 备份信息列表
111
+ */
112
+ export function listBackups(filePath) {
113
+ const backups = getBackupFiles(filePath);
114
+ return backups.map((backup) => {
115
+ try {
116
+ const stat = fs.statSync(backup.path);
117
+ return {
118
+ ...backup,
119
+ modifiedAt: stat.mtime,
120
+ size: stat.size,
121
+ };
122
+ }
123
+ catch {
124
+ return {
125
+ ...backup,
126
+ modifiedAt: new Date(0),
127
+ size: 0,
128
+ };
129
+ }
130
+ });
131
+ }
132
+ /**
133
+ * 从备份恢复配置
134
+ *
135
+ * @param filePath 原配置文件路径
136
+ * @param backupNumber 备份编号(1-5),默认恢复最新的备份(编号 1)
137
+ * @returns 是否恢复成功
138
+ */
139
+ export function restoreConfig(filePath, backupNumber = 1) {
140
+ const backupPath = `${filePath}.bak.${backupNumber}`;
141
+ if (!fs.existsSync(backupPath)) {
142
+ logger.error({ backupPath }, '备份文件不存在');
143
+ return false;
144
+ }
145
+ try {
146
+ // 先备份当前配置(如果存在)
147
+ if (fs.existsSync(filePath)) {
148
+ const currentBackupPath = `${filePath}.before-restore`;
149
+ fs.copyFileSync(filePath, currentBackupPath);
150
+ logger.debug({ path: currentBackupPath }, '已保存恢复前的配置');
151
+ }
152
+ // 恢复备份
153
+ fs.copyFileSync(backupPath, filePath);
154
+ logger.info({ from: backupPath, to: filePath }, '配置已从备份恢复');
155
+ return true;
156
+ }
157
+ catch (err) {
158
+ logger.error({ backupPath, filePath, err }, '恢复配置失败');
159
+ return false;
160
+ }
161
+ }
162
+ // ============================================================================
163
+ // JSON 文件操作
164
+ // ============================================================================
165
+ /**
166
+ * 加载 JSON 文件
167
+ *
168
+ * @param filePath - JSON 文件路径
169
+ * @param defaultValue - 文件不存在或解析失败时的默认值
170
+ * @returns 解析后的 JSON 对象
171
+ */
172
+ export function loadJson(filePath, defaultValue) {
173
+ try {
174
+ if (fs.existsSync(filePath)) {
175
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
176
+ }
177
+ }
178
+ catch {
179
+ // Return default on error
180
+ }
181
+ return defaultValue;
182
+ }
183
+ /**
184
+ * 加载 JSON 文件并替换环境变量
185
+ *
186
+ * 支持的环境变量语法:
187
+ * - ${VAR} - 从环境变量获取值
188
+ * - ${VAR:-default} - 有默认值的环境变量
189
+ *
190
+ * @param filePath - JSON 文件路径
191
+ * @param defaultValue - 文件不存在或解析失败时的默认值
192
+ * @param env - 环境变量对象 (默认使用 process.env)
193
+ * @returns 替换环境变量后的 JSON 对象
194
+ *
195
+ * @example
196
+ * // config.json: { "apiUrl": "${API_URL:-http://localhost:3000}" }
197
+ * loadJsonWithEnv('config.json', {})
198
+ * // 返回: { "apiUrl": "http://localhost:3000" } (如果 API_URL 未定义)
199
+ */
200
+ export function loadJsonWithEnv(filePath, defaultValue, env = process.env) {
201
+ const data = loadJson(filePath, defaultValue);
202
+ return substituteEnvVarsDeep(data, env);
203
+ }
204
+ /**
205
+ * 保存 JSON 配置文件
206
+ * 写入前自动创建备份,备份失败不阻塞写入
207
+ *
208
+ * @param filePath 文件路径
209
+ * @param data 要保存的数据
210
+ * @param backupOptions 备份选项
211
+ */
212
+ export function saveJson(filePath, data, backupOptions) {
213
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
214
+ // 写入前备份(备份失败只记录警告,不阻塞写入)
215
+ backupConfig(filePath, backupOptions);
216
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
217
+ }
218
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}