@spaceflow/core 0.1.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/CHANGELOG.md +1176 -0
- package/README.md +105 -0
- package/nest-cli.json +10 -0
- package/package.json +128 -0
- package/rspack.config.mjs +62 -0
- package/src/__mocks__/@opencode-ai/sdk.js +9 -0
- package/src/__mocks__/c12.ts +3 -0
- package/src/app.module.ts +18 -0
- package/src/config/ci.config.ts +29 -0
- package/src/config/config-loader.ts +101 -0
- package/src/config/config-reader.module.ts +16 -0
- package/src/config/config-reader.service.ts +133 -0
- package/src/config/feishu.config.ts +35 -0
- package/src/config/git-provider.config.ts +29 -0
- package/src/config/index.ts +29 -0
- package/src/config/llm.config.ts +110 -0
- package/src/config/schema-generator.service.ts +129 -0
- package/src/config/spaceflow.config.ts +292 -0
- package/src/config/storage.config.ts +33 -0
- package/src/extension-system/extension.interface.ts +221 -0
- package/src/extension-system/index.ts +1 -0
- package/src/index.ts +80 -0
- package/src/locales/en/translation.json +11 -0
- package/src/locales/zh-cn/translation.json +11 -0
- package/src/shared/claude-setup/claude-setup.module.ts +8 -0
- package/src/shared/claude-setup/claude-setup.service.ts +131 -0
- package/src/shared/claude-setup/index.ts +2 -0
- package/src/shared/editor-config/index.ts +23 -0
- package/src/shared/feishu-sdk/feishu-sdk.module.ts +77 -0
- package/src/shared/feishu-sdk/feishu-sdk.service.ts +130 -0
- package/src/shared/feishu-sdk/fieshu-card.service.ts +139 -0
- package/src/shared/feishu-sdk/index.ts +4 -0
- package/src/shared/feishu-sdk/types/card-action.ts +132 -0
- package/src/shared/feishu-sdk/types/card.ts +64 -0
- package/src/shared/feishu-sdk/types/common.ts +22 -0
- package/src/shared/feishu-sdk/types/index.ts +46 -0
- package/src/shared/feishu-sdk/types/message.ts +35 -0
- package/src/shared/feishu-sdk/types/module.ts +21 -0
- package/src/shared/feishu-sdk/types/user.ts +77 -0
- package/src/shared/git-provider/adapters/gitea.adapter.spec.ts +473 -0
- package/src/shared/git-provider/adapters/gitea.adapter.ts +499 -0
- package/src/shared/git-provider/adapters/github.adapter.spec.ts +341 -0
- package/src/shared/git-provider/adapters/github.adapter.ts +830 -0
- package/src/shared/git-provider/adapters/gitlab.adapter.ts +839 -0
- package/src/shared/git-provider/adapters/index.ts +3 -0
- package/src/shared/git-provider/detect-provider.spec.ts +195 -0
- package/src/shared/git-provider/detect-provider.ts +112 -0
- package/src/shared/git-provider/git-provider.interface.ts +188 -0
- package/src/shared/git-provider/git-provider.module.ts +73 -0
- package/src/shared/git-provider/git-provider.service.spec.ts +282 -0
- package/src/shared/git-provider/git-provider.service.ts +309 -0
- package/src/shared/git-provider/index.ts +7 -0
- package/src/shared/git-provider/parse-repo-url.spec.ts +221 -0
- package/src/shared/git-provider/parse-repo-url.ts +155 -0
- package/src/shared/git-provider/types.ts +434 -0
- package/src/shared/git-sdk/git-sdk-diff.utils.spec.ts +344 -0
- package/src/shared/git-sdk/git-sdk-diff.utils.ts +151 -0
- package/src/shared/git-sdk/git-sdk.module.ts +8 -0
- package/src/shared/git-sdk/git-sdk.service.ts +235 -0
- package/src/shared/git-sdk/git-sdk.types.ts +25 -0
- package/src/shared/git-sdk/index.ts +4 -0
- package/src/shared/i18n/i18n.spec.ts +96 -0
- package/src/shared/i18n/i18n.ts +86 -0
- package/src/shared/i18n/index.ts +1 -0
- package/src/shared/i18n/locale-detect.ts +134 -0
- package/src/shared/llm-jsonput/index.ts +94 -0
- package/src/shared/llm-jsonput/types.ts +17 -0
- package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +131 -0
- package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +208 -0
- package/src/shared/llm-proxy/adapters/index.ts +4 -0
- package/src/shared/llm-proxy/adapters/llm-adapter.interface.ts +23 -0
- package/src/shared/llm-proxy/adapters/open-code.adapter.ts +342 -0
- package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +215 -0
- package/src/shared/llm-proxy/adapters/openai.adapter.ts +153 -0
- package/src/shared/llm-proxy/index.ts +6 -0
- package/src/shared/llm-proxy/interfaces/config.interface.ts +32 -0
- package/src/shared/llm-proxy/interfaces/index.ts +4 -0
- package/src/shared/llm-proxy/interfaces/message.interface.ts +48 -0
- package/src/shared/llm-proxy/interfaces/session.interface.ts +28 -0
- package/src/shared/llm-proxy/llm-proxy.module.ts +140 -0
- package/src/shared/llm-proxy/llm-proxy.service.spec.ts +303 -0
- package/src/shared/llm-proxy/llm-proxy.service.ts +132 -0
- package/src/shared/llm-proxy/llm-session.spec.ts +111 -0
- package/src/shared/llm-proxy/llm-session.ts +109 -0
- package/src/shared/llm-proxy/stream-logger.ts +97 -0
- package/src/shared/logger/index.ts +11 -0
- package/src/shared/logger/logger.interface.ts +93 -0
- package/src/shared/logger/logger.spec.ts +178 -0
- package/src/shared/logger/logger.ts +175 -0
- package/src/shared/logger/renderers/plain.renderer.ts +116 -0
- package/src/shared/logger/renderers/tui.renderer.ts +162 -0
- package/src/shared/mcp/index.ts +332 -0
- package/src/shared/output/index.ts +2 -0
- package/src/shared/output/output.module.ts +9 -0
- package/src/shared/output/output.service.ts +97 -0
- package/src/shared/package-manager/index.ts +115 -0
- package/src/shared/parallel/index.ts +1 -0
- package/src/shared/parallel/parallel-executor.ts +169 -0
- package/src/shared/rspack-config/index.ts +1 -0
- package/src/shared/rspack-config/rspack-config.ts +157 -0
- package/src/shared/source-utils/index.ts +130 -0
- package/src/shared/spaceflow-dir/index.ts +158 -0
- package/src/shared/storage/adapters/file.adapter.ts +113 -0
- package/src/shared/storage/adapters/index.ts +3 -0
- package/src/shared/storage/adapters/memory.adapter.ts +50 -0
- package/src/shared/storage/adapters/storage-adapter.interface.ts +48 -0
- package/src/shared/storage/index.ts +4 -0
- package/src/shared/storage/storage.module.ts +150 -0
- package/src/shared/storage/storage.service.ts +293 -0
- package/src/shared/storage/types.ts +51 -0
- package/src/shared/verbose/index.ts +73 -0
- package/test/app.e2e-spec.ts +22 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +25 -0
- package/tsconfig.skill.json +18 -0
- package/vitest.config.ts +58 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { Inject, Injectable, OnModuleDestroy } from "@nestjs/common";
|
|
2
|
+
import { type StorageAdapter } from "./adapters/storage-adapter.interface";
|
|
3
|
+
import { STORAGE_ADAPTER, STORAGE_MODULE_OPTIONS, type StorageModuleOptions } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Storage 服务
|
|
7
|
+
* 提供统一的键值存储接口,支持过期时间和层级 key
|
|
8
|
+
*
|
|
9
|
+
* Key 使用 : 作为层级分隔符,例如:
|
|
10
|
+
* - user:123:profile
|
|
11
|
+
* - cache:api:users
|
|
12
|
+
*/
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class StorageService implements OnModuleDestroy {
|
|
15
|
+
protected cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
@Inject(STORAGE_ADAPTER)
|
|
19
|
+
protected readonly adapter: StorageAdapter,
|
|
20
|
+
@Inject(STORAGE_MODULE_OPTIONS)
|
|
21
|
+
protected readonly options: StorageModuleOptions,
|
|
22
|
+
) {
|
|
23
|
+
// 启动定期清理过期项的定时器
|
|
24
|
+
this.startCleanupTimer();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onModuleDestroy() {
|
|
28
|
+
if (this.cleanupTimer) {
|
|
29
|
+
clearInterval(this.cleanupTimer);
|
|
30
|
+
this.cleanupTimer = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 启动定期清理过期项的定时器
|
|
36
|
+
*/
|
|
37
|
+
protected startCleanupTimer(): void {
|
|
38
|
+
// 每分钟清理一次过期项
|
|
39
|
+
this.cleanupTimer = setInterval(() => {
|
|
40
|
+
this.cleanup().catch(console.error);
|
|
41
|
+
}, 60 * 1000);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 清理所有过期项
|
|
46
|
+
*/
|
|
47
|
+
async cleanup(): Promise<number> {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const allKeys = await this.adapter.keys();
|
|
50
|
+
let cleaned = 0;
|
|
51
|
+
|
|
52
|
+
for (const key of allKeys) {
|
|
53
|
+
const item = await this.adapter.get(key);
|
|
54
|
+
if (item && item.expireAt && item.expireAt <= now) {
|
|
55
|
+
await this.adapter.delete(key);
|
|
56
|
+
cleaned++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return cleaned;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取值
|
|
65
|
+
* @param key 键名,支持 : 分隔的层级结构
|
|
66
|
+
* @returns 值,如果不存在或已过期返回 undefined
|
|
67
|
+
*/
|
|
68
|
+
async get<T = any>(key: string): Promise<T | undefined> {
|
|
69
|
+
const item = await this.adapter.get<T>(key);
|
|
70
|
+
|
|
71
|
+
if (!item) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 检查是否过期
|
|
76
|
+
if (item.expireAt && item.expireAt <= Date.now()) {
|
|
77
|
+
await this.adapter.delete(key);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return item.value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 设置值
|
|
86
|
+
* @param key 键名,支持 : 分隔的层级结构
|
|
87
|
+
* @param value 值
|
|
88
|
+
* @param ttl 过期时间(毫秒),0 或 undefined 表示使用默认值
|
|
89
|
+
*/
|
|
90
|
+
async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {
|
|
91
|
+
const effectiveTtl = ttl ?? this.options.defaultTtl ?? 0;
|
|
92
|
+
|
|
93
|
+
// 检查是否需要淘汰
|
|
94
|
+
if (this.options.maxKeys && this.options.maxKeys > 0) {
|
|
95
|
+
const exists = await this.adapter.has(key);
|
|
96
|
+
if (!exists) {
|
|
97
|
+
await this.evictIfNeeded();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await this.adapter.set(key, {
|
|
102
|
+
value,
|
|
103
|
+
expireAt: effectiveTtl > 0 ? Date.now() + effectiveTtl : undefined,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 如果超过最大 key 数量,淘汰最早过期的 key
|
|
109
|
+
*/
|
|
110
|
+
protected async evictIfNeeded(): Promise<void> {
|
|
111
|
+
const maxKeys = this.options.maxKeys;
|
|
112
|
+
if (!maxKeys || maxKeys <= 0) return;
|
|
113
|
+
|
|
114
|
+
const currentSize = await this.adapter.size();
|
|
115
|
+
if (currentSize < maxKeys) return;
|
|
116
|
+
|
|
117
|
+
// 先清理过期的 key
|
|
118
|
+
await this.cleanup();
|
|
119
|
+
|
|
120
|
+
// 再次检查
|
|
121
|
+
const sizeAfterCleanup = await this.adapter.size();
|
|
122
|
+
if (sizeAfterCleanup < maxKeys) return;
|
|
123
|
+
|
|
124
|
+
// 仍然超过限制,淘汰最早过期的 key
|
|
125
|
+
const allKeys = await this.adapter.keys();
|
|
126
|
+
const keyWithExpire: { key: string; expireAt: number }[] = [];
|
|
127
|
+
|
|
128
|
+
for (const key of allKeys) {
|
|
129
|
+
const item = await this.adapter.get(key);
|
|
130
|
+
if (item) {
|
|
131
|
+
keyWithExpire.push({
|
|
132
|
+
key,
|
|
133
|
+
expireAt: item.expireAt ?? Infinity, // 永不过期的放最后
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 按过期时间排序,最早过期的在前面
|
|
139
|
+
keyWithExpire.sort((a, b) => a.expireAt - b.expireAt);
|
|
140
|
+
|
|
141
|
+
// 淘汰超出的 key
|
|
142
|
+
const toEvict = sizeAfterCleanup - maxKeys + 1; // +1 为新 key 腾出空间
|
|
143
|
+
for (let i = 0; i < toEvict && i < keyWithExpire.length; i++) {
|
|
144
|
+
await this.adapter.delete(keyWithExpire[i].key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 删除值
|
|
150
|
+
* @param key 键名
|
|
151
|
+
* @returns 是否删除成功
|
|
152
|
+
*/
|
|
153
|
+
async del(key: string): Promise<boolean> {
|
|
154
|
+
return this.adapter.delete(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 检查键是否存在(且未过期)
|
|
159
|
+
* @param key 键名
|
|
160
|
+
*/
|
|
161
|
+
async has(key: string): Promise<boolean> {
|
|
162
|
+
const item = await this.adapter.get(key);
|
|
163
|
+
|
|
164
|
+
if (!item) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 检查是否过期
|
|
169
|
+
if (item.expireAt && item.expireAt <= Date.now()) {
|
|
170
|
+
await this.adapter.delete(key);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 获取匹配的键名列表
|
|
179
|
+
* @param pattern 匹配模式,支持 * 通配符
|
|
180
|
+
*
|
|
181
|
+
* 示例:
|
|
182
|
+
* - keys('user:*') 获取所有以 user: 开头的键
|
|
183
|
+
* - keys('*:profile') 获取所有以 :profile 结尾的键
|
|
184
|
+
* - keys('user:*:settings') 获取匹配模式的键
|
|
185
|
+
*/
|
|
186
|
+
async keys(pattern?: string): Promise<string[]> {
|
|
187
|
+
const allKeys = await this.adapter.keys(pattern);
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
const validKeys: string[] = [];
|
|
190
|
+
|
|
191
|
+
// 过滤掉已过期的键
|
|
192
|
+
for (const key of allKeys) {
|
|
193
|
+
const item = await this.adapter.get(key);
|
|
194
|
+
if (item && (!item.expireAt || item.expireAt > now)) {
|
|
195
|
+
validKeys.push(key);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return validKeys;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 获取指定前缀下的所有键
|
|
204
|
+
* @param prefix 前缀,例如 'user:123'
|
|
205
|
+
*/
|
|
206
|
+
async keysWithPrefix(prefix: string): Promise<string[]> {
|
|
207
|
+
return this.keys(`${prefix}:*`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 删除指定前缀下的所有键
|
|
212
|
+
* @param prefix 前缀
|
|
213
|
+
* @returns 删除的键数量
|
|
214
|
+
*/
|
|
215
|
+
async delByPrefix(prefix: string): Promise<number> {
|
|
216
|
+
const keysToDelete = await this.keysWithPrefix(prefix);
|
|
217
|
+
let deleted = 0;
|
|
218
|
+
|
|
219
|
+
for (const key of keysToDelete) {
|
|
220
|
+
if (await this.adapter.delete(key)) {
|
|
221
|
+
deleted++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return deleted;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 设置过期时间
|
|
230
|
+
* @param key 键名
|
|
231
|
+
* @param ttl 过期时间(毫秒)
|
|
232
|
+
* @returns 是否设置成功
|
|
233
|
+
*/
|
|
234
|
+
async expire(key: string, ttl: number): Promise<boolean> {
|
|
235
|
+
const item = await this.adapter.get(key);
|
|
236
|
+
|
|
237
|
+
if (!item) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 检查是否已过期
|
|
242
|
+
if (item.expireAt && item.expireAt <= Date.now()) {
|
|
243
|
+
await this.adapter.delete(key);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await this.adapter.set(key, {
|
|
248
|
+
...item,
|
|
249
|
+
expireAt: ttl > 0 ? Date.now() + ttl : undefined,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 获取剩余过期时间
|
|
257
|
+
* @param key 键名
|
|
258
|
+
* @returns 剩余时间(毫秒),-1 表示永不过期,undefined 表示键不存在
|
|
259
|
+
*/
|
|
260
|
+
async ttl(key: string): Promise<number | undefined> {
|
|
261
|
+
const item = await this.adapter.get(key);
|
|
262
|
+
|
|
263
|
+
if (!item) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 检查是否已过期
|
|
268
|
+
if (item.expireAt && item.expireAt <= Date.now()) {
|
|
269
|
+
await this.adapter.delete(key);
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!item.expireAt) {
|
|
274
|
+
return -1; // 永不过期
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return item.expireAt - Date.now();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 清空所有存储
|
|
282
|
+
*/
|
|
283
|
+
async clear(): Promise<void> {
|
|
284
|
+
await this.adapter.clear();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 获取当前存储的 key 数量
|
|
289
|
+
*/
|
|
290
|
+
async size(): Promise<number> {
|
|
291
|
+
return this.adapter.size();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage 模块配置选项
|
|
3
|
+
*/
|
|
4
|
+
export interface StorageModuleOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 适配器类型
|
|
7
|
+
*/
|
|
8
|
+
adapter: "memory" | "file";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 文件适配器的存储路径(仅 file 适配器需要)
|
|
12
|
+
*/
|
|
13
|
+
filePath?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 默认过期时间(毫秒),0 表示永不过期
|
|
17
|
+
*/
|
|
18
|
+
defaultTtl?: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 最大 key 数量,超过时会淘汰最早过期的 key
|
|
22
|
+
* 0 或 undefined 表示不限制
|
|
23
|
+
*/
|
|
24
|
+
maxKeys?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 异步模块配置选项
|
|
29
|
+
*/
|
|
30
|
+
export interface StorageModuleAsyncOptions {
|
|
31
|
+
useFactory: (...args: any[]) => Promise<StorageModuleOptions> | StorageModuleOptions;
|
|
32
|
+
inject?: any[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 存储项元数据
|
|
37
|
+
*/
|
|
38
|
+
export interface StorageItem<T = any> {
|
|
39
|
+
value: T;
|
|
40
|
+
expireAt?: number; // 过期时间戳,undefined 表示永不过期
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 模块配置注入 Token
|
|
45
|
+
*/
|
|
46
|
+
export const STORAGE_MODULE_OPTIONS = Symbol("STORAGE_MODULE_OPTIONS");
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 适配器注入 Token
|
|
50
|
+
*/
|
|
51
|
+
export const STORAGE_ADAPTER = Symbol("STORAGE_ADAPTER");
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志级别(字符串模式)
|
|
3
|
+
* - "silent": 静默模式,不输出任何日志
|
|
4
|
+
* - "info": 显示过程日志(如 "开始审查"、"完成审查")
|
|
5
|
+
* - "verbose": 显示详细日志
|
|
6
|
+
* - "debug": 显示调试日志(包括大模型的输入提示词和输出过程)
|
|
7
|
+
*/
|
|
8
|
+
export type LogLevel = "silent" | "info" | "verbose" | "debug";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Verbose 日志级别(数字模式,向后兼容)
|
|
12
|
+
* - 0 / false: 静默模式,只返回结果
|
|
13
|
+
* - 1 / true: 显示过程日志
|
|
14
|
+
* - 2: 显示详细日志
|
|
15
|
+
* - 3: 显示调试日志
|
|
16
|
+
*/
|
|
17
|
+
export type VerboseLevel = 0 | 1 | 2 | 3 | false | true;
|
|
18
|
+
|
|
19
|
+
/** 日志级别优先级映射 */
|
|
20
|
+
export const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
21
|
+
silent: 0,
|
|
22
|
+
info: 1,
|
|
23
|
+
verbose: 2,
|
|
24
|
+
debug: 3,
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
/** VerboseLevel 数字到 LogLevel 字符串的映射 */
|
|
28
|
+
const VERBOSE_TO_LOG_LEVEL: Record<number, LogLevel> = {
|
|
29
|
+
0: "silent",
|
|
30
|
+
1: "info",
|
|
31
|
+
2: "verbose",
|
|
32
|
+
3: "debug",
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 将任意级别值统一转为 LogLevel 字符串
|
|
37
|
+
* @param level 日志级别(字符串、数字、布尔值)
|
|
38
|
+
* @returns LogLevel 字符串
|
|
39
|
+
*/
|
|
40
|
+
export const toLogLevel = (level: LogLevel | VerboseLevel | undefined): LogLevel => {
|
|
41
|
+
if (level === undefined) return "info";
|
|
42
|
+
if (typeof level === "string") return level;
|
|
43
|
+
if (level === true) return "info";
|
|
44
|
+
if (level === false) return "silent";
|
|
45
|
+
return VERBOSE_TO_LOG_LEVEL[level] ?? "info";
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 将 verbose 值规范化为数字
|
|
50
|
+
* @param verbose verbose 值(支持字符串、数字、布尔值)
|
|
51
|
+
* @returns 规范化后的数字 (0, 1, 2, 3)
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeVerbose(
|
|
54
|
+
verbose: LogLevel | VerboseLevel | boolean | undefined,
|
|
55
|
+
): 0 | 1 | 2 | 3 {
|
|
56
|
+
if (verbose === undefined || verbose === false || verbose === 0 || verbose === "silent") return 0;
|
|
57
|
+
if (verbose === true || verbose === 1 || verbose === "info") return 1;
|
|
58
|
+
if (verbose === 2 || verbose === "verbose") return 2;
|
|
59
|
+
return 3;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 判断是否应该打印指定级别的日志
|
|
64
|
+
* @param verbose 当前 verbose 级别(支持字符串或数字)
|
|
65
|
+
* @param requiredLevel 需要的最低级别 (1, 2, 3)
|
|
66
|
+
* @returns 是否应该打印
|
|
67
|
+
*/
|
|
68
|
+
export function shouldLog(
|
|
69
|
+
verbose: LogLevel | VerboseLevel | undefined,
|
|
70
|
+
requiredLevel: 1 | 2 | 3,
|
|
71
|
+
): boolean {
|
|
72
|
+
return normalizeVerbose(verbose) >= requiredLevel;
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Test, TestingModule } from "@nestjs/testing";
|
|
2
|
+
import { INestApplication } from "@nestjs/common";
|
|
3
|
+
import * as request from "supertest";
|
|
4
|
+
import { App } from "supertest/types";
|
|
5
|
+
import { AppModule } from "./../src/app.module";
|
|
6
|
+
|
|
7
|
+
describe("AppController (e2e)", () => {
|
|
8
|
+
let app: INestApplication<App>;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
12
|
+
imports: [AppModule],
|
|
13
|
+
}).compile();
|
|
14
|
+
|
|
15
|
+
app = moduleFixture.createNestApplication();
|
|
16
|
+
await app.init();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("/ (GET)", () => {
|
|
20
|
+
return request(app.getHttpServer()).get("/").expect(200).expect("Hello World!");
|
|
21
|
+
});
|
|
22
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"removeComments": true,
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"target": "ES2023",
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"baseUrl": "./",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"strictNullChecks": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"noImplicitAny": false,
|
|
20
|
+
"strictBindCallApply": false,
|
|
21
|
+
"noFallthroughCasesInSwitch": false,
|
|
22
|
+
"resolveJsonModule": true,
|
|
23
|
+
"types": ["vitest/globals"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"lib": ["ES2022"],
|
|
8
|
+
"strict": false,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"experimentalDecorators": true,
|
|
13
|
+
"emitDecoratorMetadata": true,
|
|
14
|
+
"declaration": false,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
}
|
|
18
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import swc from "unplugin-swc";
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [swc.vite()],
|
|
6
|
+
test: {
|
|
7
|
+
root: "src",
|
|
8
|
+
globals: true,
|
|
9
|
+
environment: "node",
|
|
10
|
+
include: ["**/*.spec.ts"],
|
|
11
|
+
alias: {
|
|
12
|
+
"^src/(.*)$": "$1",
|
|
13
|
+
},
|
|
14
|
+
coverage: {
|
|
15
|
+
provider: "v8",
|
|
16
|
+
include: ["shared/**/*.ts"],
|
|
17
|
+
exclude: [
|
|
18
|
+
"**/*.spec.ts",
|
|
19
|
+
"**/*.module.ts",
|
|
20
|
+
"**/index.ts",
|
|
21
|
+
"**/__mocks__/**",
|
|
22
|
+
"**/*.interface.ts",
|
|
23
|
+
"**/*.types.ts",
|
|
24
|
+
"**/types.ts",
|
|
25
|
+
"**/types/**",
|
|
26
|
+
"**/interfaces/**",
|
|
27
|
+
"**/feishu-sdk/**",
|
|
28
|
+
"**/storage/**",
|
|
29
|
+
"**/rspack-config/**",
|
|
30
|
+
"**/output/**",
|
|
31
|
+
"**/parallel/**",
|
|
32
|
+
"**/mcp/**",
|
|
33
|
+
"**/editor-config/**",
|
|
34
|
+
"**/package-manager/**",
|
|
35
|
+
"**/source-utils/**",
|
|
36
|
+
"**/spaceflow-dir/**",
|
|
37
|
+
"**/verbose/**",
|
|
38
|
+
"**/claude-setup/**",
|
|
39
|
+
"**/git-sdk/git-sdk.service.ts",
|
|
40
|
+
"**/i18n/locale-detect.ts",
|
|
41
|
+
"**/tui.renderer.ts",
|
|
42
|
+
"**/llm-jsonput/**",
|
|
43
|
+
"**/stream-logger.ts",
|
|
44
|
+
"**/open-code.adapter.ts",
|
|
45
|
+
"**/claude-code.adapter.ts",
|
|
46
|
+
"**/gitlab.adapter.ts",
|
|
47
|
+
"**/gitea.adapter.ts",
|
|
48
|
+
"**/github.adapter.ts",
|
|
49
|
+
],
|
|
50
|
+
thresholds: {
|
|
51
|
+
lines: 80,
|
|
52
|
+
functions: 80,
|
|
53
|
+
branches: 80,
|
|
54
|
+
statements: 80,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|