specflow-dev-service 0.0.0-beta.2
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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +95 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +10 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +69 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +68 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +49 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +274 -0
- package/dist/logger.js.map +1 -0
- package/dist/orchestrator.d.ts +57 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +650 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/runner/client.d.ts +13 -0
- package/dist/runner/client.d.ts.map +1 -0
- package/dist/runner/client.js +25 -0
- package/dist/runner/client.js.map +1 -0
- package/package.json +47 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 精简日志系统
|
|
5
|
+
*
|
|
6
|
+
* 设计原则:
|
|
7
|
+
* - 默认模式:只显示进度条 + URL,不刷屏
|
|
8
|
+
* - verbose 模式:展示全部详细信息
|
|
9
|
+
* - 错误始终展示
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// 颜色工具(轻量,不依赖 chalk)
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
const isColor = process.stdout.isTTY !== false;
|
|
17
|
+
process.stdout.isTTY === true;
|
|
18
|
+
const c = {
|
|
19
|
+
reset: isColor ? '\x1b[0m' : '',
|
|
20
|
+
bold: isColor ? '\x1b[1m' : '',
|
|
21
|
+
dim: isColor ? '\x1b[2m' : '',
|
|
22
|
+
green: isColor ? '\x1b[32m' : '',
|
|
23
|
+
yellow: isColor ? '\x1b[33m' : '',
|
|
24
|
+
red: isColor ? '\x1b[31m' : '',
|
|
25
|
+
cyan: isColor ? '\x1b[36m' : '',
|
|
26
|
+
blue: isColor ? '\x1b[34m' : '',
|
|
27
|
+
gray: isColor ? '\x1b[90m' : ''};
|
|
28
|
+
|
|
29
|
+
// ============================================================
|
|
30
|
+
// 图标
|
|
31
|
+
// ============================================================
|
|
32
|
+
|
|
33
|
+
const icons = {
|
|
34
|
+
success: isColor ? '✓' : '[OK]',
|
|
35
|
+
error: isColor ? '✗' : '[ERR]',
|
|
36
|
+
warn: isColor ? '⚠' : '[WARN]',
|
|
37
|
+
info: isColor ? '●' : '[*]',
|
|
38
|
+
arrow: isColor ? '→' : '->'};
|
|
39
|
+
|
|
40
|
+
// ============================================================
|
|
41
|
+
// 模式控制
|
|
42
|
+
// ============================================================
|
|
43
|
+
|
|
44
|
+
const LOG_LEVELS = {
|
|
45
|
+
silent: 0,
|
|
46
|
+
error: 1,
|
|
47
|
+
warn: 2,
|
|
48
|
+
info: 3,
|
|
49
|
+
debug: 4
|
|
50
|
+
};
|
|
51
|
+
let currentLevel = 'info';
|
|
52
|
+
let verboseMode = false;
|
|
53
|
+
|
|
54
|
+
/** 设置日志级别 */
|
|
55
|
+
function setLogLevel(level) {
|
|
56
|
+
currentLevel = level;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 设置 verbose 模式(展示全部详细信息) */
|
|
60
|
+
function setVerbose(v) {
|
|
61
|
+
verboseMode = v;
|
|
62
|
+
}
|
|
63
|
+
function shouldLog(level) {
|
|
64
|
+
return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// 核心输出
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 服务启动消息(始终展示——这是用户最需要看到的)
|
|
73
|
+
*
|
|
74
|
+
* 输出示例:
|
|
75
|
+
* ✓ Dev Service http://localhost:3001
|
|
76
|
+
* ✓ Client http://localhost:2029
|
|
77
|
+
*/
|
|
78
|
+
function logServiceReady(name, url, extra) {
|
|
79
|
+
if (!shouldLog('info')) return;
|
|
80
|
+
const tag = `${c.green}${icons.success}${c.reset}`;
|
|
81
|
+
const svcName = `${c.bold}${padRight(name, 8)}${c.reset}`;
|
|
82
|
+
const link = `${c.cyan}${url}${c.reset}`;
|
|
83
|
+
const suffix = extra ? ` ${c.dim}${extra}${c.reset}` : '';
|
|
84
|
+
console.log(` ${tag} ${svcName} ${link}${suffix}`);
|
|
85
|
+
logBus.emit({
|
|
86
|
+
level: 'success',
|
|
87
|
+
module: name,
|
|
88
|
+
message: `${url}${extra ? ` ${extra}` : ''}`,
|
|
89
|
+
timestamp: Date.now()
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 进度消息(仅 verbose 模式展示)
|
|
95
|
+
*/
|
|
96
|
+
function logProgress(module, action, detail) {
|
|
97
|
+
if (!shouldLog('info')) return;
|
|
98
|
+
if (verboseMode) {
|
|
99
|
+
const tag = `${c.blue}${icons.info}${c.reset}`;
|
|
100
|
+
const mod = `${c.bold}${padRight(module, 10)}${c.reset}`;
|
|
101
|
+
const act = `${c.dim}${action}${c.reset}`;
|
|
102
|
+
const suffix = detail ? ` ${c.dim}${detail}${c.reset}` : '';
|
|
103
|
+
console.log(` ${tag} ${mod} ${act}${suffix}`);
|
|
104
|
+
}
|
|
105
|
+
logBus.emit({
|
|
106
|
+
level: 'info',
|
|
107
|
+
module,
|
|
108
|
+
message: action,
|
|
109
|
+
detail,
|
|
110
|
+
timestamp: Date.now()
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 文件变化通知(始终展示)
|
|
116
|
+
*/
|
|
117
|
+
function logWatch(file, action) {
|
|
118
|
+
if (!shouldLog('info')) return;
|
|
119
|
+
const tag = `${c.yellow}${icons.arrow}${c.reset}`;
|
|
120
|
+
const label = `${c.bold}${padRight('watch', 10)}${c.reset}`;
|
|
121
|
+
console.log(` ${tag} ${label} ${c.dim}${file} ${action}${c.reset}`);
|
|
122
|
+
logBus.emit({
|
|
123
|
+
level: 'info',
|
|
124
|
+
module: 'watch',
|
|
125
|
+
message: `${file} ${action}`,
|
|
126
|
+
timestamp: Date.now()
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 警告(始终展示)
|
|
132
|
+
*/
|
|
133
|
+
function logWarn(msg) {
|
|
134
|
+
if (!shouldLog('warn')) return;
|
|
135
|
+
console.log(` ${c.yellow}${icons.warn}${c.reset} ${c.yellow}${msg}${c.reset}`);
|
|
136
|
+
logBus.emit({
|
|
137
|
+
level: 'warn',
|
|
138
|
+
module: 'system',
|
|
139
|
+
message: msg,
|
|
140
|
+
timestamp: Date.now()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 错误(展示详情)
|
|
146
|
+
*/
|
|
147
|
+
function logError(msg, err) {
|
|
148
|
+
if (!shouldLog('error')) return;
|
|
149
|
+
console.log(` ${c.red}${icons.error} ${msg}${c.reset}`);
|
|
150
|
+
let detail;
|
|
151
|
+
if (err) {
|
|
152
|
+
detail = err instanceof Error ? err.message : err;
|
|
153
|
+
// 错误详情缩进展示(不再递归调用 logError)
|
|
154
|
+
for (const line of detail.split('\n')) {
|
|
155
|
+
console.log(` ${c.dim}${line}${c.reset}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// debug 模式下展示堆栈
|
|
159
|
+
if (currentLevel === 'debug' && err instanceof Error && err.stack) {
|
|
160
|
+
const stackLines = err.stack.split('\n').slice(1);
|
|
161
|
+
for (const line of stackLines) {
|
|
162
|
+
console.log(` ${c.gray}${line.trim()}${c.reset}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
logBus.emit({
|
|
167
|
+
level: 'error',
|
|
168
|
+
module: 'system',
|
|
169
|
+
message: msg,
|
|
170
|
+
detail,
|
|
171
|
+
timestamp: Date.now()
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================
|
|
176
|
+
// 分隔与标题
|
|
177
|
+
// ============================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 启动 banner(极简版)
|
|
181
|
+
*/
|
|
182
|
+
function logBanner(command) {
|
|
183
|
+
if (!shouldLog('info')) return;
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(` ${c.bold}specflow ${command}${c.reset}`);
|
|
186
|
+
console.log(` ${c.dim}${'─'.repeat(20)}${c.reset}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 完成消息
|
|
191
|
+
*/
|
|
192
|
+
function logReady(startTime) {
|
|
193
|
+
if (!shouldLog('info')) return;
|
|
194
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(` ${c.green}${icons.success} Ready in ${elapsed}s${c.reset}`);
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================
|
|
201
|
+
// 工具函数
|
|
202
|
+
// ============================================================
|
|
203
|
+
|
|
204
|
+
function padRight(s, len) {
|
|
205
|
+
return s.length >= len ? s : s + ' '.repeat(len - s.length);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================
|
|
209
|
+
// LogBus — 全局日志事件总线
|
|
210
|
+
// ============================================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 全局日志事件总线
|
|
214
|
+
*
|
|
215
|
+
* 所有 logXxx 函数在输出终端的同时,都会向此总线推送事件。
|
|
216
|
+
* AdminServer 订阅此总线,通过 SSE 将日志实时推送给订阅者(前端/模型)。
|
|
217
|
+
*/
|
|
218
|
+
class LogBus {
|
|
219
|
+
listeners = new Set();
|
|
220
|
+
/** 最近 N 条日志缓存(用于新连接时回放) */
|
|
221
|
+
recentLogs = [];
|
|
222
|
+
constructor(maxRecent = 200) {
|
|
223
|
+
this.maxRecent = maxRecent;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 订阅日志事件 */
|
|
227
|
+
subscribe(listener) {
|
|
228
|
+
this.listeners.add(listener);
|
|
229
|
+
return () => this.listeners.delete(listener);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 推送日志事件(内部调用) */
|
|
233
|
+
emit(event) {
|
|
234
|
+
// 缓存最近日志
|
|
235
|
+
this.recentLogs.push(event);
|
|
236
|
+
if (this.recentLogs.length > this.maxRecent) {
|
|
237
|
+
this.recentLogs.shift();
|
|
238
|
+
}
|
|
239
|
+
// 通知所有订阅者
|
|
240
|
+
for (const listener of this.listeners) {
|
|
241
|
+
try {
|
|
242
|
+
listener(event);
|
|
243
|
+
} catch {
|
|
244
|
+
// 忽略监听器错误
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 获取最近日志(新连接时回放) */
|
|
250
|
+
getRecent(limit = 50) {
|
|
251
|
+
return this.recentLogs.slice(-limit);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 清空缓存 */
|
|
255
|
+
clear() {
|
|
256
|
+
this.recentLogs = [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** 全局单例 LogBus */
|
|
261
|
+
const logBus = new LogBus();
|
|
262
|
+
|
|
263
|
+
exports.LogBus = LogBus;
|
|
264
|
+
exports.logBanner = logBanner;
|
|
265
|
+
exports.logBus = logBus;
|
|
266
|
+
exports.logError = logError;
|
|
267
|
+
exports.logProgress = logProgress;
|
|
268
|
+
exports.logReady = logReady;
|
|
269
|
+
exports.logServiceReady = logServiceReady;
|
|
270
|
+
exports.logWarn = logWarn;
|
|
271
|
+
exports.logWatch = logWatch;
|
|
272
|
+
exports.setLogLevel = setLogLevel;
|
|
273
|
+
exports.setVerbose = setVerbose;
|
|
274
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sources":["../src/logger.ts"],"sourcesContent":["/**\n * 精简日志系统\n *\n * 设计原则:\n * - 默认模式:只显示进度条 + URL,不刷屏\n * - verbose 模式:展示全部详细信息\n * - 错误始终展示\n */\n\n// ============================================================\n// 颜色工具(轻量,不依赖 chalk)\n// ============================================================\n\nconst isColor = process.stdout.isTTY !== false;\nconst isTTY = process.stdout.isTTY === true;\n\nconst c = {\n reset: isColor ? '\\x1b[0m' : '',\n bold: isColor ? '\\x1b[1m' : '',\n dim: isColor ? '\\x1b[2m' : '',\n green: isColor ? '\\x1b[32m' : '',\n yellow: isColor ? '\\x1b[33m' : '',\n red: isColor ? '\\x1b[31m' : '',\n cyan: isColor ? '\\x1b[36m' : '',\n blue: isColor ? '\\x1b[34m' : '',\n magenta: isColor ? '\\x1b[35m' : '',\n gray: isColor ? '\\x1b[90m' : '',\n clearLine: isTTY ? '\\x1b[2K\\r' : '',\n};\n\n// ============================================================\n// 图标\n// ============================================================\n\nconst icons = {\n success: isColor ? '✓' : '[OK]',\n error: isColor ? '✗' : '[ERR]',\n warn: isColor ? '⚠' : '[WARN]',\n info: isColor ? '●' : '[*]',\n arrow: isColor ? '→' : '->',\n spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],\n};\n\n// ============================================================\n// 模式控制\n// ============================================================\n\nexport type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4,\n};\n\nlet currentLevel: LogLevel = 'info';\nlet verboseMode = false;\n\n/** 设置日志级别 */\nexport function setLogLevel(level: LogLevel): void {\n currentLevel = level;\n}\n\n/** 设置 verbose 模式(展示全部详细信息) */\nexport function setVerbose(v: boolean): void {\n verboseMode = v;\n}\n\n/** 是否为 verbose 模式 */\nexport function isVerbose(): boolean {\n return verboseMode;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] <= LOG_LEVELS[currentLevel];\n}\n\n// ============================================================\n// 进度条\n// ============================================================\n\nexport interface ProgressStep {\n label: string;\n status: 'pending' | 'running' | 'done' | 'error';\n detail?: string;\n}\n\n/**\n * 启动进度条\n *\n * 非 verbose 模式下:覆盖式单行显示进度\n * verbose 模式下:每步一行\n */\nexport class ProgressBar {\n private steps: ProgressStep[] = [];\n private spinnerFrame = 0;\n private spinnerTimer: ReturnType<typeof setInterval> | null = null;\n private startTime = Date.now();\n /** 记录哪些步骤已经在非 TTY 模式下打印过 */\n private printedStepIds = new Set<number>();\n /** 当前是否有未换行的进度行占着终端(TTY 覆盖模式) */\n private hasInlineLine = false;\n\n constructor(private title: string) {}\n\n /** 添加步骤 */\n addStep(label: string): number {\n const idx = this.steps.length;\n this.steps.push({ label, status: 'pending' });\n return idx;\n }\n\n /** 开始一个步骤 */\n startStep(idx: number, detail?: string): void {\n this.steps[idx].status = 'running';\n this.steps[idx].detail = detail;\n\n if (verboseMode) {\n this.printVerboseStep(idx);\n } else {\n this.renderCompact();\n }\n }\n\n /** 完成一个步骤 */\n completeStep(idx: number, detail?: string): void {\n this.steps[idx].status = 'done';\n if (detail !== undefined) this.steps[idx].detail = detail;\n\n if (verboseMode) {\n this.printVerboseStepDone(idx);\n } else {\n this.renderCompact();\n }\n }\n\n /** 步骤出错 */\n errorStep(idx: number, detail?: string): void {\n this.steps[idx].status = 'error';\n if (detail !== undefined) this.steps[idx].detail = detail;\n\n if (verboseMode) {\n this.printVerboseStepError(idx);\n } else {\n this.renderCompact();\n }\n }\n\n /** 启动动画(仅 TTY) */\n startSpinner(): void {\n if (!isTTY || verboseMode) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerFrame = (this.spinnerFrame + 1) % icons.spinner.length;\n this.renderCompact();\n }, 80);\n }\n\n /** 停止动画 */\n stopSpinner(): void {\n if (this.spinnerTimer) {\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n }\n\n /** 完成,打印最终摘要 */\n finish(): void {\n this.stopSpinner();\n const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);\n\n // 清掉 TTY 覆盖行(如果有的话)\n this.clearInline();\n\n // 打印完成行\n const doneCount = this.steps.filter((s) => s.status === 'done').length;\n const errCount = this.steps.filter((s) => s.status === 'error').length;\n\n if (errCount > 0) {\n console.log(\n ` ${c.yellow}${icons.warn}${c.reset} ${c.bold}${this.title}${c.reset} ${c.dim}${doneCount}/${this.steps.length} done, ${errCount} errors${c.reset} ${c.dim}${elapsed}s${c.reset}`,\n );\n } else {\n console.log(\n ` ${c.green}${icons.success}${c.reset} ${c.bold}${this.title}${c.reset} ${c.dim}${doneCount}/${this.steps.length} steps${c.reset} ${c.dim}${elapsed}s${c.reset}`,\n );\n }\n }\n\n // ---------- 内部方法 ----------\n\n /** 清除 TTY 模式下的覆盖行 */\n private clearInline(): void {\n if (this.hasInlineLine && isTTY) {\n process.stdout.write('\\r\\x1b[2K');\n this.hasInlineLine = false;\n }\n }\n\n /**\n * 非 verbose 渲染\n *\n * - TTY 终端:覆盖式单行(spinner + 进度条),无换行\n * - 非 TTY(VSCode 终端 / CI):每个步骤完成时打印一行,无 spinner 动画\n */\n private renderCompact(): void {\n if (!shouldLog('info')) return;\n\n if (!isTTY) {\n this.renderNonTty();\n return;\n }\n\n this.renderTty();\n }\n\n private renderNonTty(): void {\n for (let i = 0; i < this.steps.length; i++) {\n if (this.steps[i].status === 'done' && !this.printedStepIds.has(i)) {\n this.printedStepIds.add(i);\n const step = this.steps[i];\n const detail = step.detail ? ` ${c.dim}${step.detail}${c.reset}` : '';\n console.log(` ${c.green}${icons.success}${c.reset} ${c.bold}${padRight(step.label, 10)}${c.reset}${detail}`);\n }\n }\n }\n\n private renderTty(): void {\n const running = this.steps.find((s) => s.status === 'running');\n const doneCount = this.steps.filter((s) => s.status === 'done').length;\n const total = this.steps.length;\n const pct = total > 0 ? Math.round((doneCount / total) * 100) : 0;\n\n const barWidth = 15;\n const filled = Math.round((doneCount / Math.max(total, 1)) * barWidth);\n const bar = `${c.green}${'█'.repeat(filled)}${c.dim}${'░'.repeat(barWidth - filled)}${c.reset}`;\n\n const spinner = running\n ? `${c.cyan}${icons.spinner[this.spinnerFrame]}${c.reset} `\n : `${c.green}${icons.success}${c.reset} `;\n\n const label = running ? running.label : 'done';\n const detail = running?.detail ? ` ${c.dim}${running.detail}${c.reset}` : '';\n\n process.stdout.write(`\\r\\x1b[2K ${spinner}${bar} ${c.dim}${pct}%${c.reset} ${c.bold}${label}${c.reset}${detail}`);\n this.hasInlineLine = true;\n }\n\n /** verbose:逐行打印 */\n private printVerboseStep(idx: number): void {\n if (!shouldLog('info')) return;\n const step = this.steps[idx];\n const detail = step.detail ? ` ${c.dim}${step.detail}${c.reset}` : '';\n console.log(` ${c.blue}${icons.info}${c.reset} ${c.bold}${padRight(step.label, 10)}${c.reset}${detail}`);\n }\n\n private printVerboseStepDone(idx: number): void {\n if (!shouldLog('info')) return;\n const step = this.steps[idx];\n const detail = step.detail ? ` ${c.dim}${step.detail}${c.reset}` : '';\n console.log(` ${c.green}${icons.success}${c.reset} ${c.bold}${padRight(step.label, 10)}${c.reset}${detail}`);\n }\n\n private printVerboseStepError(idx: number): void {\n if (!shouldLog('error')) return;\n const step = this.steps[idx];\n const detail = step.detail ? ` ${c.dim}${step.detail}${c.reset}` : '';\n console.log(` ${c.red}${icons.error}${c.reset} ${c.bold}${padRight(step.label, 10)}${c.reset}${detail}`);\n }\n}\n\n// ============================================================\n// 核心输出\n// ============================================================\n\n/**\n * 服务启动消息(始终展示——这是用户最需要看到的)\n *\n * 输出示例:\n * ✓ Dev Service http://localhost:3001\n * ✓ Client http://localhost:2029\n */\nexport function logServiceReady(name: string, url: string, extra?: string): void {\n if (!shouldLog('info')) return;\n\n const tag = `${c.green}${icons.success}${c.reset}`;\n const svcName = `${c.bold}${padRight(name, 8)}${c.reset}`;\n const link = `${c.cyan}${url}${c.reset}`;\n const suffix = extra ? ` ${c.dim}${extra}${c.reset}` : '';\n\n console.log(` ${tag} ${svcName} ${link}${suffix}`);\n\n logBus.emit({\n level: 'success',\n module: name,\n message: `${url}${extra ? ` ${extra}` : ''}`,\n timestamp: Date.now(),\n });\n}\n\n/**\n * 进度消息(仅 verbose 模式展示)\n */\nexport function logProgress(module: string, action: string, detail?: string): void {\n if (!shouldLog('info')) return;\n\n if (verboseMode) {\n const tag = `${c.blue}${icons.info}${c.reset}`;\n const mod = `${c.bold}${padRight(module, 10)}${c.reset}`;\n const act = `${c.dim}${action}${c.reset}`;\n const suffix = detail ? ` ${c.dim}${detail}${c.reset}` : '';\n console.log(` ${tag} ${mod} ${act}${suffix}`);\n }\n\n logBus.emit({\n level: 'info',\n module,\n message: action,\n detail,\n timestamp: Date.now(),\n });\n}\n\n/**\n * 文件变化通知(始终展示)\n */\nexport function logWatch(file: string, action: string): void {\n if (!shouldLog('info')) return;\n\n const tag = `${c.yellow}${icons.arrow}${c.reset}`;\n const label = `${c.bold}${padRight('watch', 10)}${c.reset}`;\n\n console.log(` ${tag} ${label} ${c.dim}${file} ${action}${c.reset}`);\n\n logBus.emit({\n level: 'info',\n module: 'watch',\n message: `${file} ${action}`,\n timestamp: Date.now(),\n });\n}\n\n/**\n * 警告(始终展示)\n */\nexport function logWarn(msg: string): void {\n if (!shouldLog('warn')) return;\n console.log(` ${c.yellow}${icons.warn}${c.reset} ${c.yellow}${msg}${c.reset}`);\n\n logBus.emit({\n level: 'warn',\n module: 'system',\n message: msg,\n timestamp: Date.now(),\n });\n}\n\n/**\n * 错误(展示详情)\n */\nexport function logError(msg: string, err?: Error | string): void {\n if (!shouldLog('error')) return;\n console.log(` ${c.red}${icons.error} ${msg}${c.reset}`);\n\n let detail: string | undefined;\n if (err) {\n detail = err instanceof Error ? err.message : err;\n // 错误详情缩进展示(不再递归调用 logError)\n for (const line of detail.split('\\n')) {\n console.log(` ${c.dim}${line}${c.reset}`);\n }\n\n // debug 模式下展示堆栈\n if (currentLevel === 'debug' && err instanceof Error && err.stack) {\n const stackLines = err.stack.split('\\n').slice(1);\n for (const line of stackLines) {\n console.log(` ${c.gray}${line.trim()}${c.reset}`);\n }\n }\n }\n\n logBus.emit({\n level: 'error',\n module: 'system',\n message: msg,\n detail,\n timestamp: Date.now(),\n });\n}\n\n/**\n * 致命错误(抛出异常)\n */\nexport function logFatal(msg: string, err?: Error | string): never {\n logError(msg, err);\n throw new FatalError(msg, toErrorCause(err));\n}\n\n/** 致命错误类型 */\nclass FatalError extends Error {\n constructor(\n message: string,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = 'FatalError';\n }\n}\n\n/** 转换错误原因 */\nfunction toErrorCause(err?: Error | string): Error | undefined {\n if (err instanceof Error) return err;\n if (err) return new Error(err);\n return undefined;\n}\n\n// ============================================================\n// 分隔与标题\n// ============================================================\n\n/**\n * 启动 banner(极简版)\n */\nexport function logBanner(command: string): void {\n if (!shouldLog('info')) return;\n console.log();\n console.log(` ${c.bold}specflow ${command}${c.reset}`);\n console.log(` ${c.dim}${'─'.repeat(20)}${c.reset}`);\n}\n\n/**\n * 完成消息\n */\nexport function logReady(startTime: number): void {\n if (!shouldLog('info')) return;\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n console.log();\n console.log(` ${c.green}${icons.success} Ready in ${elapsed}s${c.reset}`);\n console.log();\n}\n\n/**\n * debug 日志(仅 debug 模式)\n */\nexport function logDebug(msg: string): void {\n if (!shouldLog('debug')) return;\n console.log(` ${c.gray}[debug] ${msg}${c.reset}`);\n}\n\n// ============================================================\n// 空行\n// ============================================================\n\nexport function logNewline(): void {\n if (!shouldLog('info')) return;\n console.log();\n}\n\n// ============================================================\n// 工具函数\n// ============================================================\n\nfunction padRight(s: string, len: number): string {\n return s.length >= len ? s : s + ' '.repeat(len - s.length);\n}\n\n// ============================================================\n// LogBus — 全局日志事件总线\n// ============================================================\n\nexport type LogEventLevel = 'info' | 'warn' | 'error' | 'success' | 'debug';\n\nexport interface LogEvent {\n /** 日志级别 */\n level: LogEventLevel;\n /** 模块标签(如 'handler', 'codegen', 'watcher') */\n module: string;\n /** 日志消息 */\n message: string;\n /** 附加详情(如错误堆栈、文件路径) */\n detail?: string;\n /** 时间戳(ms) */\n timestamp: number;\n}\n\ntype LogListener = (event: LogEvent) => void;\n\n/**\n * 全局日志事件总线\n *\n * 所有 logXxx 函数在输出终端的同时,都会向此总线推送事件。\n * AdminServer 订阅此总线,通过 SSE 将日志实时推送给订阅者(前端/模型)。\n */\nexport class LogBus {\n private listeners: Set<LogListener> = new Set();\n /** 最近 N 条日志缓存(用于新连接时回放) */\n private recentLogs: LogEvent[] = [];\n private maxRecent: number;\n\n constructor(maxRecent = 200) {\n this.maxRecent = maxRecent;\n }\n\n /** 订阅日志事件 */\n subscribe(listener: LogListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** 推送日志事件(内部调用) */\n emit(event: LogEvent): void {\n // 缓存最近日志\n this.recentLogs.push(event);\n if (this.recentLogs.length > this.maxRecent) {\n this.recentLogs.shift();\n }\n // 通知所有订阅者\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch {\n // 忽略监听器错误\n }\n }\n }\n\n /** 获取最近日志(新连接时回放) */\n getRecent(limit = 50): LogEvent[] {\n return this.recentLogs.slice(-limit);\n }\n\n /** 清空缓存 */\n clear(): void {\n this.recentLogs = [];\n }\n}\n\n/** 全局单例 LogBus */\nexport const logBus = new LogBus();\n"],"names":["isColor","process","stdout","isTTY","c","reset","bold","dim","green","yellow","red","cyan","blue","magenta","gray","icons","success","error","warn","info","arrow","LOG_LEVELS","silent","debug","currentLevel","verboseMode","setLogLevel","level","setVerbose","v","shouldLog","logServiceReady","name","url","extra","tag","svcName","padRight","link","suffix","console","log","logBus","emit","module","message","timestamp","Date","now","logProgress","action","detail","mod","act","logWatch","file","label","logWarn","msg","logError","err","Error","line","split","stack","stackLines","slice","trim","logBanner","command","repeat","logReady","startTime","elapsed","toFixed","s","len","length","LogBus","listeners","Set","recentLogs","constructor","maxRecent","subscribe","listener","add","delete","event","push","shift","getRecent","limit","clear"],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ResolvedConfig } from './config/schema.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ForgeService — 核心协调器
|
|
5
|
+
*
|
|
6
|
+
* 职责:
|
|
7
|
+
* 1. 解析 specflow.config.ts 配置文件
|
|
8
|
+
* 2. 前端启动逻辑(端口管理 + Vite 进程管理)
|
|
9
|
+
* 3. 后端热更新逻辑(air)
|
|
10
|
+
*
|
|
11
|
+
* 日志原则:只输出关键信息,静默处理正常流程
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface ForgeServiceStatus {
|
|
15
|
+
running: boolean;
|
|
16
|
+
config: {
|
|
17
|
+
clientPort: number;
|
|
18
|
+
servicePort: number;
|
|
19
|
+
};
|
|
20
|
+
client: {
|
|
21
|
+
port: number;
|
|
22
|
+
running: boolean;
|
|
23
|
+
pid?: number;
|
|
24
|
+
};
|
|
25
|
+
service: {
|
|
26
|
+
port: number;
|
|
27
|
+
running: boolean;
|
|
28
|
+
pid?: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
declare class ForgeService {
|
|
32
|
+
onLog?: (level: 'info' | 'warn' | 'error' | 'success', msg: string) => void;
|
|
33
|
+
private config;
|
|
34
|
+
private cwd;
|
|
35
|
+
private running;
|
|
36
|
+
private clientHandle;
|
|
37
|
+
private goManager;
|
|
38
|
+
constructor(config: ResolvedConfig, cwd: string);
|
|
39
|
+
/** 停止所有服务 */
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
/** 获取状态 */
|
|
42
|
+
getStatus(): ForgeServiceStatus;
|
|
43
|
+
/** 启动所有服务(Go 后端先启动,前端后启动) */
|
|
44
|
+
start(options?: {
|
|
45
|
+
client?: boolean;
|
|
46
|
+
service?: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
private getClientStatus;
|
|
49
|
+
private getServiceStatus;
|
|
50
|
+
private waitForPort;
|
|
51
|
+
private startGoService;
|
|
52
|
+
}
|
|
53
|
+
declare function createForgeService(cwd?: string, configPath?: string): Promise<ForgeService>;
|
|
54
|
+
|
|
55
|
+
export { ForgeService, createForgeService };
|
|
56
|
+
export type { ForgeServiceStatus };
|
|
57
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sources":["../src/orchestrator.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,UAAU,kBAAkB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,YAAY;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA,YAAY,OAAO;AACnB;AACA,iBAAiB,kBAAkB;AACnC;AACA;AACA;AACA;AACA,QAAQ,OAAO;AACf;AACA;AACA;AACA;AACA;AACA,iBAAiB,kBAAkB,qCAAqC,OAAO,CAAC,YAAY;;;;","names":[]}
|