@curl-runner/cli 1.16.0 → 1.16.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/package.json +2 -2
- package/src/ci-exit.test.ts +0 -216
- package/src/cli.ts +0 -1351
- package/src/commands/upgrade.ts +0 -262
- package/src/diff/baseline-manager.test.ts +0 -181
- package/src/diff/baseline-manager.ts +0 -266
- package/src/diff/diff-formatter.ts +0 -316
- package/src/diff/index.ts +0 -3
- package/src/diff/response-differ.test.ts +0 -330
- package/src/diff/response-differ.ts +0 -489
- package/src/executor/max-concurrency.test.ts +0 -139
- package/src/executor/profile-executor.test.ts +0 -132
- package/src/executor/profile-executor.ts +0 -167
- package/src/executor/request-executor.ts +0 -663
- package/src/parser/yaml.test.ts +0 -480
- package/src/parser/yaml.ts +0 -271
- package/src/snapshot/index.ts +0 -3
- package/src/snapshot/snapshot-differ.test.ts +0 -358
- package/src/snapshot/snapshot-differ.ts +0 -296
- package/src/snapshot/snapshot-formatter.ts +0 -170
- package/src/snapshot/snapshot-manager.test.ts +0 -204
- package/src/snapshot/snapshot-manager.ts +0 -342
- package/src/types/bun-yaml.d.ts +0 -11
- package/src/types/config.ts +0 -638
- package/src/utils/colors.ts +0 -30
- package/src/utils/condition-evaluator.test.ts +0 -415
- package/src/utils/condition-evaluator.ts +0 -327
- package/src/utils/curl-builder.test.ts +0 -165
- package/src/utils/curl-builder.ts +0 -209
- package/src/utils/installation-detector.test.ts +0 -52
- package/src/utils/installation-detector.ts +0 -123
- package/src/utils/logger.ts +0 -856
- package/src/utils/response-store.test.ts +0 -213
- package/src/utils/response-store.ts +0 -108
- package/src/utils/stats.test.ts +0 -161
- package/src/utils/stats.ts +0 -151
- package/src/utils/version-checker.ts +0 -158
- package/src/version.ts +0 -43
- package/src/watcher/file-watcher.test.ts +0 -186
- package/src/watcher/file-watcher.ts +0 -140
package/src/utils/logger.ts
DELETED
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
import { SnapshotFormatter } from '../snapshot/snapshot-formatter';
|
|
2
|
-
import type {
|
|
3
|
-
ExecutionResult,
|
|
4
|
-
ExecutionSummary,
|
|
5
|
-
GlobalConfig,
|
|
6
|
-
ProfileResult,
|
|
7
|
-
RequestConfig,
|
|
8
|
-
} from '../types/config';
|
|
9
|
-
import { generateHistogram } from './stats';
|
|
10
|
-
|
|
11
|
-
interface TreeNode {
|
|
12
|
-
label: string;
|
|
13
|
-
value?: string;
|
|
14
|
-
children?: TreeNode[];
|
|
15
|
-
color?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class TreeRenderer {
|
|
19
|
-
private colors: Record<string, string>;
|
|
20
|
-
|
|
21
|
-
constructor(colors: Record<string, string>) {
|
|
22
|
-
this.colors = colors;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private color(text: string, colorName: string): string {
|
|
26
|
-
if (!colorName || !this.colors[colorName]) {
|
|
27
|
-
return text;
|
|
28
|
-
}
|
|
29
|
-
return `${this.colors[colorName]}${text}${this.colors.reset}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
render(nodes: TreeNode[], basePrefix: string = ' '): void {
|
|
33
|
-
nodes.forEach((node, index) => {
|
|
34
|
-
const isLast = index === nodes.length - 1;
|
|
35
|
-
const prefix = isLast ? `${basePrefix}└─` : `${basePrefix}├─`;
|
|
36
|
-
|
|
37
|
-
if (node.label && node.value) {
|
|
38
|
-
// Regular labeled node with value
|
|
39
|
-
const displayValue = node.color ? this.color(node.value, node.color) : node.value;
|
|
40
|
-
|
|
41
|
-
// Handle multiline values (like Response Body)
|
|
42
|
-
const lines = displayValue.split('\n');
|
|
43
|
-
if (lines.length === 1) {
|
|
44
|
-
console.log(`${prefix} ${node.label}: ${displayValue}`);
|
|
45
|
-
} else {
|
|
46
|
-
console.log(`${prefix} ${node.label}:`);
|
|
47
|
-
const contentPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
|
|
48
|
-
lines.forEach((line) => {
|
|
49
|
-
console.log(`${contentPrefix}${line}`);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
} else if (node.label && !node.value) {
|
|
53
|
-
// Section header (like "Headers:" or "Metrics:")
|
|
54
|
-
console.log(`${prefix} ${node.label}:`);
|
|
55
|
-
} else if (!node.label && node.value) {
|
|
56
|
-
// Content line without label (like response body lines)
|
|
57
|
-
const continuationPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
|
|
58
|
-
console.log(`${continuationPrefix}${node.value}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (node.children && node.children.length > 0) {
|
|
62
|
-
const childPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
|
|
63
|
-
this.render(node.children, childPrefix);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export class Logger {
|
|
70
|
-
private config: GlobalConfig['output'];
|
|
71
|
-
|
|
72
|
-
private readonly colors = {
|
|
73
|
-
reset: '\x1b[0m',
|
|
74
|
-
bright: '\x1b[1m',
|
|
75
|
-
dim: '\x1b[2m',
|
|
76
|
-
underscore: '\x1b[4m',
|
|
77
|
-
|
|
78
|
-
black: '\x1b[30m',
|
|
79
|
-
red: '\x1b[31m',
|
|
80
|
-
green: '\x1b[32m',
|
|
81
|
-
yellow: '\x1b[33m',
|
|
82
|
-
blue: '\x1b[34m',
|
|
83
|
-
magenta: '\x1b[35m',
|
|
84
|
-
cyan: '\x1b[36m',
|
|
85
|
-
white: '\x1b[37m',
|
|
86
|
-
|
|
87
|
-
bgBlack: '\x1b[40m',
|
|
88
|
-
bgRed: '\x1b[41m',
|
|
89
|
-
bgGreen: '\x1b[42m',
|
|
90
|
-
bgYellow: '\x1b[43m',
|
|
91
|
-
bgBlue: '\x1b[44m',
|
|
92
|
-
bgMagenta: '\x1b[45m',
|
|
93
|
-
bgCyan: '\x1b[46m',
|
|
94
|
-
bgWhite: '\x1b[47m',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
constructor(config: GlobalConfig['output'] = {}) {
|
|
98
|
-
this.config = {
|
|
99
|
-
verbose: false,
|
|
100
|
-
showHeaders: false,
|
|
101
|
-
showBody: true,
|
|
102
|
-
showMetrics: false,
|
|
103
|
-
format: 'pretty',
|
|
104
|
-
prettyLevel: 'minimal',
|
|
105
|
-
...config,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
color(text: string, color: keyof typeof this.colors): string {
|
|
110
|
-
return `${this.colors[color]}${text}${this.colors.reset}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private getShortFilename(filePath: string): string {
|
|
114
|
-
return filePath.replace(/.*\//, '').replace('.yaml', '');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private shouldShowOutput(): boolean {
|
|
118
|
-
if (this.config.format === 'raw') {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
if (this.config.format === 'pretty') {
|
|
122
|
-
return true; // Pretty format should always show output
|
|
123
|
-
}
|
|
124
|
-
return this.config.verbose !== false; // For other formats, respect verbose flag
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private shouldShowHeaders(): boolean {
|
|
128
|
-
if (this.config.format !== 'pretty') {
|
|
129
|
-
return this.config.showHeaders || false;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const level = this.config.prettyLevel || 'standard';
|
|
133
|
-
switch (level) {
|
|
134
|
-
case 'minimal':
|
|
135
|
-
return false;
|
|
136
|
-
case 'standard':
|
|
137
|
-
return this.config.showHeaders || false;
|
|
138
|
-
case 'detailed':
|
|
139
|
-
return true;
|
|
140
|
-
default:
|
|
141
|
-
return this.config.showHeaders || false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private shouldShowBody(): boolean {
|
|
146
|
-
if (this.config.format !== 'pretty') {
|
|
147
|
-
return this.config.showBody !== false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const level = this.config.prettyLevel || 'standard';
|
|
151
|
-
switch (level) {
|
|
152
|
-
case 'minimal':
|
|
153
|
-
return false; // Minimal never shows body
|
|
154
|
-
case 'standard':
|
|
155
|
-
return this.config.showBody !== false;
|
|
156
|
-
case 'detailed':
|
|
157
|
-
return true; // Detailed always shows body
|
|
158
|
-
default:
|
|
159
|
-
return this.config.showBody !== false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private shouldShowMetrics(): boolean {
|
|
164
|
-
if (this.config.format !== 'pretty') {
|
|
165
|
-
return this.config.showMetrics || false;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const level = this.config.prettyLevel || 'standard';
|
|
169
|
-
switch (level) {
|
|
170
|
-
case 'minimal':
|
|
171
|
-
return false; // Minimal never shows metrics
|
|
172
|
-
case 'standard':
|
|
173
|
-
return this.config.showMetrics || false;
|
|
174
|
-
case 'detailed':
|
|
175
|
-
return true; // Detailed always shows metrics
|
|
176
|
-
default:
|
|
177
|
-
return this.config.showMetrics || false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private shouldShowRequestDetails(): boolean {
|
|
182
|
-
if (this.config.format !== 'pretty') {
|
|
183
|
-
return this.config.verbose || false;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const level = this.config.prettyLevel || 'standard';
|
|
187
|
-
switch (level) {
|
|
188
|
-
case 'minimal':
|
|
189
|
-
return false;
|
|
190
|
-
case 'standard':
|
|
191
|
-
return this.config.verbose || false;
|
|
192
|
-
case 'detailed':
|
|
193
|
-
return true;
|
|
194
|
-
default:
|
|
195
|
-
return this.config.verbose || false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private shouldShowSeparators(): boolean {
|
|
200
|
-
if (this.config.format !== 'pretty') {
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const level = this.config.prettyLevel || 'standard';
|
|
205
|
-
switch (level) {
|
|
206
|
-
case 'minimal':
|
|
207
|
-
return false;
|
|
208
|
-
case 'standard':
|
|
209
|
-
return true;
|
|
210
|
-
case 'detailed':
|
|
211
|
-
return true;
|
|
212
|
-
default:
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private colorStatusCode(statusStr: string): string {
|
|
218
|
-
// For expected status codes in validation errors, use yellow to distinguish from red actual values
|
|
219
|
-
return this.color(statusStr, 'yellow');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private logValidationErrors(errorString: string): void {
|
|
223
|
-
// Check if this is a validation error with multiple parts (separated by ';')
|
|
224
|
-
const errors = errorString.split('; ');
|
|
225
|
-
|
|
226
|
-
if (errors.length === 1) {
|
|
227
|
-
// Single error - check if it's a status error for special formatting
|
|
228
|
-
const trimmedError = errors[0].trim();
|
|
229
|
-
const statusMatch = trimmedError.match(/^Expected status (.+?), got (.+)$/);
|
|
230
|
-
if (statusMatch) {
|
|
231
|
-
const [, expected, actual] = statusMatch;
|
|
232
|
-
const expectedStatus = this.colorStatusCode(expected.replace(' or ', '|'));
|
|
233
|
-
const actualStatus = this.color(actual, 'red'); // Always red for incorrect actual values
|
|
234
|
-
console.log(
|
|
235
|
-
` ${this.color('✗', 'red')} ${this.color('Error:', 'red')} Expected ${this.color('status', 'yellow')} ${expectedStatus}, got ${actualStatus}`,
|
|
236
|
-
);
|
|
237
|
-
} else {
|
|
238
|
-
console.log(` ${this.color('✗', 'red')} ${this.color('Error:', 'red')} ${trimmedError}`);
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
// Multiple validation errors - show them nicely formatted
|
|
242
|
-
console.log(` ${this.color('✗', 'red')} ${this.color('Validation Errors:', 'red')}`);
|
|
243
|
-
for (const error of errors) {
|
|
244
|
-
const trimmedError = error.trim();
|
|
245
|
-
if (trimmedError) {
|
|
246
|
-
// Parse different error formats for better formatting
|
|
247
|
-
if (trimmedError.startsWith('Expected ')) {
|
|
248
|
-
// Format 1: "Expected status 201, got 200"
|
|
249
|
-
const statusMatch = trimmedError.match(/^Expected status (.+?), got (.+)$/);
|
|
250
|
-
if (statusMatch) {
|
|
251
|
-
const [, expected, actual] = statusMatch;
|
|
252
|
-
const expectedStatus = this.colorStatusCode(expected.replace(' or ', '|'));
|
|
253
|
-
const actualStatus = this.color(actual, 'red'); // Always red for incorrect actual values
|
|
254
|
-
console.log(
|
|
255
|
-
` ${this.color('•', 'red')} ${this.color('status', 'yellow')}: expected ${expectedStatus}, got ${actualStatus}`,
|
|
256
|
-
);
|
|
257
|
-
} else {
|
|
258
|
-
// Format 2: "Expected field to be value, got value"
|
|
259
|
-
const fieldMatch = trimmedError.match(/^Expected (.+?) to be (.+?), got (.+)$/);
|
|
260
|
-
if (fieldMatch) {
|
|
261
|
-
const [, field, expected, actual] = fieldMatch;
|
|
262
|
-
console.log(
|
|
263
|
-
` ${this.color('•', 'red')} ${this.color(field, 'yellow')}: expected ${this.color(expected, 'green')}, got ${this.color(actual, 'red')}`,
|
|
264
|
-
);
|
|
265
|
-
} else {
|
|
266
|
-
console.log(` ${this.color('•', 'red')} ${trimmedError}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
} else {
|
|
270
|
-
console.log(` ${this.color('•', 'red')} ${trimmedError}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private formatJson(data: unknown): string {
|
|
278
|
-
if (this.config.format === 'raw') {
|
|
279
|
-
return typeof data === 'string' ? data : JSON.stringify(data);
|
|
280
|
-
}
|
|
281
|
-
if (this.config.format === 'json') {
|
|
282
|
-
return JSON.stringify(data);
|
|
283
|
-
}
|
|
284
|
-
return JSON.stringify(data, null, 2);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private formatDuration(ms: number): string {
|
|
288
|
-
if (ms < 1000) {
|
|
289
|
-
return `${ms.toFixed(0)}ms`;
|
|
290
|
-
}
|
|
291
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private formatSize(bytes: number | undefined): string {
|
|
295
|
-
if (!bytes) {
|
|
296
|
-
return '0 B';
|
|
297
|
-
}
|
|
298
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
299
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
300
|
-
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
logExecutionStart(count: number, mode: string): void {
|
|
304
|
-
if (!this.shouldShowOutput()) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (this.shouldShowSeparators()) {
|
|
309
|
-
console.log(); // Add spacing before the execution header
|
|
310
|
-
console.log(this.color(`Executing ${count} request(s) in ${mode} mode`, 'dim'));
|
|
311
|
-
console.log();
|
|
312
|
-
} else {
|
|
313
|
-
// For minimal format, still add spacing after processing info
|
|
314
|
-
console.log();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
logRequestStart(_config: RequestConfig, _index: number): void {
|
|
319
|
-
// In the new format, we show everything in logRequestComplete
|
|
320
|
-
// This method is kept for compatibility but simplified
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
logCommand(command: string): void {
|
|
325
|
-
if (this.shouldShowRequestDetails()) {
|
|
326
|
-
console.log(this.color(' Command:', 'dim'));
|
|
327
|
-
console.log(this.color(` ${command}`, 'dim'));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
logRetry(attempt: number, maxRetries: number): void {
|
|
332
|
-
console.log(this.color(` ↻ Retry ${attempt}/${maxRetries}...`, 'yellow'));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
logRequestComplete(result: ExecutionResult): void {
|
|
336
|
-
// Handle raw format output - only show response body
|
|
337
|
-
if (this.config.format === 'raw') {
|
|
338
|
-
if (result.success && this.config.showBody && result.body) {
|
|
339
|
-
const bodyStr = this.formatJson(result.body);
|
|
340
|
-
console.log(bodyStr);
|
|
341
|
-
}
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Handle JSON format output - structured JSON only
|
|
346
|
-
if (this.config.format === 'json') {
|
|
347
|
-
const jsonResult = {
|
|
348
|
-
request: {
|
|
349
|
-
name: result.request.name,
|
|
350
|
-
url: result.request.url,
|
|
351
|
-
method: result.request.method || 'GET',
|
|
352
|
-
},
|
|
353
|
-
success: result.success,
|
|
354
|
-
status: result.status,
|
|
355
|
-
...(this.shouldShowHeaders() && result.headers ? { headers: result.headers } : {}),
|
|
356
|
-
...(this.shouldShowBody() && result.body ? { body: result.body } : {}),
|
|
357
|
-
...(result.error ? { error: result.error } : {}),
|
|
358
|
-
...(this.shouldShowMetrics() && result.metrics ? { metrics: result.metrics } : {}),
|
|
359
|
-
};
|
|
360
|
-
console.log(JSON.stringify(jsonResult, null, 2));
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Pretty format output (default behavior)
|
|
365
|
-
if (!this.shouldShowOutput()) {
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const level = this.config.prettyLevel || 'minimal';
|
|
370
|
-
const statusColor = result.success ? 'green' : 'red';
|
|
371
|
-
const statusIcon = result.success ? '✓' : 'x';
|
|
372
|
-
const name = result.request.name || 'Request';
|
|
373
|
-
|
|
374
|
-
if (level === 'minimal') {
|
|
375
|
-
// Minimal format: clean tree structure but compact
|
|
376
|
-
const fileTag = result.request.sourceFile
|
|
377
|
-
? this.getShortFilename(result.request.sourceFile)
|
|
378
|
-
: 'inline';
|
|
379
|
-
console.log(
|
|
380
|
-
`${this.color(statusIcon, statusColor)} ${this.color(name, 'bright')} [${fileTag}]`,
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
const treeNodes: TreeNode[] = [];
|
|
384
|
-
const renderer = new TreeRenderer(this.colors);
|
|
385
|
-
|
|
386
|
-
treeNodes.push({
|
|
387
|
-
label: result.request.method || 'GET',
|
|
388
|
-
value: result.request.url,
|
|
389
|
-
color: 'blue',
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const statusText = result.status ? `${result.status}` : 'ERROR';
|
|
393
|
-
treeNodes.push({
|
|
394
|
-
label: `${statusIcon} Status`,
|
|
395
|
-
value: statusText,
|
|
396
|
-
color: statusColor,
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (result.metrics) {
|
|
400
|
-
const durationSize = `${this.formatDuration(result.metrics.duration)} | ${this.formatSize(result.metrics.size)}`;
|
|
401
|
-
treeNodes.push({
|
|
402
|
-
label: 'Duration',
|
|
403
|
-
value: durationSize,
|
|
404
|
-
color: 'cyan',
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
renderer.render(treeNodes);
|
|
409
|
-
|
|
410
|
-
if (result.error) {
|
|
411
|
-
console.log();
|
|
412
|
-
this.logValidationErrors(result.error);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Show snapshot result
|
|
416
|
-
if (result.snapshotResult) {
|
|
417
|
-
console.log();
|
|
418
|
-
this.logSnapshotResult(result.request.name || 'Request', result.snapshotResult);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
console.log();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Standard and detailed formats: use clean tree structure
|
|
426
|
-
console.log(`${this.color(statusIcon, statusColor)} ${this.color(name, 'bright')}`);
|
|
427
|
-
|
|
428
|
-
// Build tree structure
|
|
429
|
-
const treeNodes: TreeNode[] = [];
|
|
430
|
-
const renderer = new TreeRenderer(this.colors);
|
|
431
|
-
|
|
432
|
-
// Main info nodes
|
|
433
|
-
treeNodes.push({ label: 'URL', value: result.request.url, color: 'blue' });
|
|
434
|
-
treeNodes.push({ label: 'Method', value: result.request.method || 'GET', color: 'yellow' });
|
|
435
|
-
treeNodes.push({
|
|
436
|
-
label: 'Status',
|
|
437
|
-
value: String(result.status || 'ERROR'),
|
|
438
|
-
color: statusColor,
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
if (result.metrics) {
|
|
442
|
-
treeNodes.push({
|
|
443
|
-
label: 'Duration',
|
|
444
|
-
value: this.formatDuration(result.metrics.duration),
|
|
445
|
-
color: 'cyan',
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Add headers section if needed
|
|
450
|
-
if (this.shouldShowHeaders() && result.headers && Object.keys(result.headers).length > 0) {
|
|
451
|
-
const headerChildren: TreeNode[] = Object.entries(result.headers).map(([key, value]) => ({
|
|
452
|
-
label: this.color(key, 'dim'),
|
|
453
|
-
value: String(value),
|
|
454
|
-
}));
|
|
455
|
-
|
|
456
|
-
treeNodes.push({
|
|
457
|
-
label: 'Headers',
|
|
458
|
-
children: headerChildren,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Add body section if needed
|
|
463
|
-
if (this.shouldShowBody() && result.body) {
|
|
464
|
-
const bodyStr = this.formatJson(result.body);
|
|
465
|
-
const lines = bodyStr.split('\n');
|
|
466
|
-
const maxLines = this.shouldShowRequestDetails() ? Infinity : 10;
|
|
467
|
-
const bodyLines = lines.slice(0, maxLines);
|
|
468
|
-
|
|
469
|
-
if (lines.length > maxLines) {
|
|
470
|
-
bodyLines.push(this.color(`... (${lines.length - maxLines} more lines)`, 'dim'));
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
treeNodes.push({
|
|
474
|
-
label: 'Response Body',
|
|
475
|
-
value: bodyLines.join('\n'),
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Add detailed metrics section if needed
|
|
480
|
-
if (this.shouldShowMetrics() && result.metrics && level === 'detailed') {
|
|
481
|
-
const metrics = result.metrics;
|
|
482
|
-
const metricChildren: TreeNode[] = [];
|
|
483
|
-
|
|
484
|
-
metricChildren.push({
|
|
485
|
-
label: 'Request Duration',
|
|
486
|
-
value: this.formatDuration(metrics.duration),
|
|
487
|
-
color: 'cyan',
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
if (metrics.size !== undefined) {
|
|
491
|
-
metricChildren.push({
|
|
492
|
-
label: 'Response Size',
|
|
493
|
-
value: this.formatSize(metrics.size),
|
|
494
|
-
color: 'cyan',
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (metrics.dnsLookup) {
|
|
499
|
-
metricChildren.push({
|
|
500
|
-
label: 'DNS Lookup',
|
|
501
|
-
value: this.formatDuration(metrics.dnsLookup),
|
|
502
|
-
color: 'cyan',
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (metrics.tcpConnection) {
|
|
507
|
-
metricChildren.push({
|
|
508
|
-
label: 'TCP Connection',
|
|
509
|
-
value: this.formatDuration(metrics.tcpConnection),
|
|
510
|
-
color: 'cyan',
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (metrics.tlsHandshake) {
|
|
515
|
-
metricChildren.push({
|
|
516
|
-
label: 'TLS Handshake',
|
|
517
|
-
value: this.formatDuration(metrics.tlsHandshake),
|
|
518
|
-
color: 'cyan',
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (metrics.firstByte) {
|
|
523
|
-
metricChildren.push({
|
|
524
|
-
label: 'Time to First Byte',
|
|
525
|
-
value: this.formatDuration(metrics.firstByte),
|
|
526
|
-
color: 'cyan',
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
treeNodes.push({
|
|
531
|
-
label: 'Metrics',
|
|
532
|
-
children: metricChildren,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Render the tree
|
|
537
|
-
renderer.render(treeNodes);
|
|
538
|
-
|
|
539
|
-
if (result.error) {
|
|
540
|
-
console.log();
|
|
541
|
-
this.logValidationErrors(result.error);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Show snapshot result
|
|
545
|
-
if (result.snapshotResult) {
|
|
546
|
-
console.log();
|
|
547
|
-
this.logSnapshotResult(result.request.name || 'Request', result.snapshotResult);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
console.log();
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Logs snapshot comparison result.
|
|
555
|
-
*/
|
|
556
|
-
private logSnapshotResult(requestName: string, result: ExecutionResult['snapshotResult']): void {
|
|
557
|
-
if (!result) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const formatter = new SnapshotFormatter();
|
|
562
|
-
console.log(formatter.formatResult(requestName, result));
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
logSummary(summary: ExecutionSummary, isGlobal: boolean = false): void {
|
|
566
|
-
// For raw format, don't show summary
|
|
567
|
-
if (this.config.format === 'raw') {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// For JSON format, output structured summary
|
|
572
|
-
if (this.config.format === 'json') {
|
|
573
|
-
const jsonSummary = {
|
|
574
|
-
summary: {
|
|
575
|
-
total: summary.total,
|
|
576
|
-
successful: summary.successful,
|
|
577
|
-
failed: summary.failed,
|
|
578
|
-
skipped: summary.skipped,
|
|
579
|
-
duration: summary.duration,
|
|
580
|
-
},
|
|
581
|
-
results: summary.results.map((result) => ({
|
|
582
|
-
request: {
|
|
583
|
-
name: result.request.name,
|
|
584
|
-
url: result.request.url,
|
|
585
|
-
method: result.request.method || 'GET',
|
|
586
|
-
},
|
|
587
|
-
success: result.success,
|
|
588
|
-
status: result.status,
|
|
589
|
-
...(this.shouldShowHeaders() && result.headers ? { headers: result.headers } : {}),
|
|
590
|
-
...(this.shouldShowBody() && result.body ? { body: result.body } : {}),
|
|
591
|
-
...(result.error ? { error: result.error } : {}),
|
|
592
|
-
...(this.shouldShowMetrics() && result.metrics ? { metrics: result.metrics } : {}),
|
|
593
|
-
})),
|
|
594
|
-
};
|
|
595
|
-
console.log(JSON.stringify(jsonSummary, null, 2));
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Pretty format summary (default behavior)
|
|
600
|
-
if (!this.shouldShowOutput()) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const level = this.config.prettyLevel || 'minimal';
|
|
605
|
-
|
|
606
|
-
// Add spacing for global summary
|
|
607
|
-
if (isGlobal) {
|
|
608
|
-
console.log(); // Extra spacing before global summary
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (level === 'minimal') {
|
|
612
|
-
// Simple one-line summary for minimal, similar to docs example
|
|
613
|
-
const statusColor = summary.failed === 0 ? 'green' : 'red';
|
|
614
|
-
const skippedText = summary.skipped > 0 ? `, ${summary.skipped} skipped` : '';
|
|
615
|
-
const successText =
|
|
616
|
-
summary.failed === 0
|
|
617
|
-
? `${summary.total} request${summary.total === 1 ? '' : 's'} completed successfully${skippedText}`
|
|
618
|
-
: `${summary.successful}/${summary.total} request${summary.total === 1 ? '' : 's'} completed, ${summary.failed} failed${skippedText}`;
|
|
619
|
-
|
|
620
|
-
const summaryPrefix = isGlobal ? '◆ Global Summary' : 'Summary';
|
|
621
|
-
console.log(`${summaryPrefix}: ${this.color(successText, statusColor)}`);
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Compact summary for standard/detailed - much simpler
|
|
626
|
-
const _successRate = ((summary.successful / summary.total) * 100).toFixed(1);
|
|
627
|
-
const statusColor = summary.failed === 0 ? 'green' : 'red';
|
|
628
|
-
const skippedText = summary.skipped > 0 ? `, ${summary.skipped} skipped` : '';
|
|
629
|
-
const successText =
|
|
630
|
-
summary.failed === 0
|
|
631
|
-
? `${summary.total} request${summary.total === 1 ? '' : 's'} completed successfully${skippedText}`
|
|
632
|
-
: `${summary.successful}/${summary.total} request${summary.total === 1 ? '' : 's'} completed, ${summary.failed} failed${skippedText}`;
|
|
633
|
-
|
|
634
|
-
const summaryPrefix = isGlobal ? '◆ Global Summary' : 'Summary';
|
|
635
|
-
console.log();
|
|
636
|
-
console.log(
|
|
637
|
-
`${summaryPrefix}: ${this.color(successText, statusColor)} (${this.color(this.formatDuration(summary.duration), 'cyan')})`,
|
|
638
|
-
);
|
|
639
|
-
|
|
640
|
-
if (summary.failed > 0 && this.shouldShowRequestDetails()) {
|
|
641
|
-
summary.results
|
|
642
|
-
.filter((r) => !r.success)
|
|
643
|
-
.forEach((r) => {
|
|
644
|
-
const name = r.request.name || r.request.url;
|
|
645
|
-
console.log(` ${this.color('•', 'red')} ${name}: ${r.error}`);
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
logError(message: string): void {
|
|
651
|
-
console.error(this.color(`✗ ${message}`, 'red'));
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
logWarning(message: string): void {
|
|
655
|
-
console.warn(this.color(`⚠ ${message}`, 'yellow'));
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
logInfo(message: string): void {
|
|
659
|
-
console.log(this.color(`ℹ ${message}`, 'blue'));
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
logSuccess(message: string): void {
|
|
663
|
-
console.log(this.color(`✓ ${message}`, 'green'));
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
logSkipped(config: RequestConfig, index: number, reason?: string): void {
|
|
667
|
-
if (!this.shouldShowOutput()) {
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const name = config.name || `Request ${index}`;
|
|
672
|
-
|
|
673
|
-
if (this.config.format === 'json') {
|
|
674
|
-
const jsonResult = {
|
|
675
|
-
request: {
|
|
676
|
-
name: config.name,
|
|
677
|
-
url: config.url,
|
|
678
|
-
method: config.method || 'GET',
|
|
679
|
-
},
|
|
680
|
-
skipped: true,
|
|
681
|
-
reason: reason || 'condition not met',
|
|
682
|
-
};
|
|
683
|
-
console.log(JSON.stringify(jsonResult, null, 2));
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Pretty format
|
|
688
|
-
console.log(
|
|
689
|
-
`${this.color('⊘', 'yellow')} ${this.color(name, 'bright')} ${this.color('[SKIP]', 'yellow')}`,
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
if (reason) {
|
|
693
|
-
const treeNodes: TreeNode[] = [{ label: 'Reason', value: reason, color: 'yellow' }];
|
|
694
|
-
const renderer = new TreeRenderer(this.colors);
|
|
695
|
-
renderer.render(treeNodes);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
console.log();
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
logFileHeader(fileName: string, requestCount: number): void {
|
|
702
|
-
if (!this.shouldShowOutput() || this.config.format !== 'pretty') {
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const shortName = fileName.replace(/.*\//, '').replace('.yaml', '');
|
|
707
|
-
console.log();
|
|
708
|
-
console.log(
|
|
709
|
-
this.color(`▶ ${shortName}.yaml`, 'bright') +
|
|
710
|
-
this.color(` (${requestCount} request${requestCount === 1 ? '' : 's'})`, 'dim'),
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
logWatch(files: string[]): void {
|
|
715
|
-
console.log();
|
|
716
|
-
console.log(
|
|
717
|
-
`${this.color('Watching for changes...', 'cyan')} ${this.color('(press Ctrl+C to stop)', 'dim')}`,
|
|
718
|
-
);
|
|
719
|
-
const fileList = files.length <= 3 ? files.join(', ') : `${files.length} files`;
|
|
720
|
-
console.log(this.color(` Files: ${fileList}`, 'dim'));
|
|
721
|
-
console.log();
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
logWatchReady(): void {
|
|
725
|
-
console.log();
|
|
726
|
-
console.log(this.color('Watching for changes...', 'cyan'));
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
logFileChanged(filename: string): void {
|
|
730
|
-
const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
731
|
-
console.log(this.color('-'.repeat(50), 'dim'));
|
|
732
|
-
console.log(
|
|
733
|
-
`${this.color(`[${timestamp}]`, 'dim')} File changed: ${this.color(filename, 'yellow')}`,
|
|
734
|
-
);
|
|
735
|
-
console.log();
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
logProfileStart(
|
|
739
|
-
requestName: string,
|
|
740
|
-
iterations: number,
|
|
741
|
-
warmup: number,
|
|
742
|
-
concurrency: number,
|
|
743
|
-
): void {
|
|
744
|
-
if (!this.shouldShowOutput()) {
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
console.log();
|
|
749
|
-
console.log(`${this.color('⚡ PROFILING', 'magenta')} ${this.color(requestName, 'bright')}`);
|
|
750
|
-
console.log(
|
|
751
|
-
this.color(
|
|
752
|
-
` ${iterations} iterations, ${warmup} warmup, concurrency: ${concurrency}`,
|
|
753
|
-
'dim',
|
|
754
|
-
),
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
logProfileResult(result: ProfileResult, showHistogram: boolean): void {
|
|
759
|
-
const { stats, request } = result;
|
|
760
|
-
const name = request.name || request.url;
|
|
761
|
-
|
|
762
|
-
if (this.config.format === 'json') {
|
|
763
|
-
console.log(
|
|
764
|
-
JSON.stringify({
|
|
765
|
-
request: { name, url: request.url, method: request.method || 'GET' },
|
|
766
|
-
stats: {
|
|
767
|
-
iterations: stats.iterations,
|
|
768
|
-
warmup: stats.warmup,
|
|
769
|
-
failures: stats.failures,
|
|
770
|
-
failureRate: stats.failureRate,
|
|
771
|
-
min: stats.min,
|
|
772
|
-
max: stats.max,
|
|
773
|
-
mean: stats.mean,
|
|
774
|
-
median: stats.median,
|
|
775
|
-
p50: stats.p50,
|
|
776
|
-
p95: stats.p95,
|
|
777
|
-
p99: stats.p99,
|
|
778
|
-
stdDev: stats.stdDev,
|
|
779
|
-
},
|
|
780
|
-
}),
|
|
781
|
-
);
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (this.config.format === 'raw') {
|
|
786
|
-
// Raw format: just print the key stats
|
|
787
|
-
console.log(`${stats.p50}\t${stats.p95}\t${stats.p99}\t${stats.mean}`);
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Pretty format
|
|
792
|
-
console.log();
|
|
793
|
-
const statusIcon = stats.failures === 0 ? this.color('✓', 'green') : this.color('⚠', 'yellow');
|
|
794
|
-
console.log(`${statusIcon} ${this.color(name, 'bright')}`);
|
|
795
|
-
|
|
796
|
-
// Latency stats table
|
|
797
|
-
console.log(this.color(' ┌─────────────────────────────────────┐', 'dim'));
|
|
798
|
-
console.log(
|
|
799
|
-
` │ ${this.color('p50', 'cyan')} ${this.formatLatency(stats.p50).padStart(10)} │ ${this.color('min', 'dim')} ${this.formatLatency(stats.min).padStart(10)} │`,
|
|
800
|
-
);
|
|
801
|
-
console.log(
|
|
802
|
-
` │ ${this.color('p95', 'yellow')} ${this.formatLatency(stats.p95).padStart(10)} │ ${this.color('max', 'dim')} ${this.formatLatency(stats.max).padStart(10)} │`,
|
|
803
|
-
);
|
|
804
|
-
console.log(
|
|
805
|
-
` │ ${this.color('p99', 'red')} ${this.formatLatency(stats.p99).padStart(10)} │ ${this.color('mean', 'dim')} ${this.formatLatency(stats.mean).padStart(10)} │`,
|
|
806
|
-
);
|
|
807
|
-
console.log(this.color(' └─────────────────────────────────────┘', 'dim'));
|
|
808
|
-
|
|
809
|
-
// Additional stats
|
|
810
|
-
console.log(
|
|
811
|
-
this.color(
|
|
812
|
-
` σ ${stats.stdDev.toFixed(2)}ms | ${stats.iterations} samples | ${stats.failures} failures (${stats.failureRate}%)`,
|
|
813
|
-
'dim',
|
|
814
|
-
),
|
|
815
|
-
);
|
|
816
|
-
|
|
817
|
-
// Optional histogram
|
|
818
|
-
if (showHistogram && stats.timings.length > 0) {
|
|
819
|
-
console.log();
|
|
820
|
-
console.log(this.color(' Distribution:', 'dim'));
|
|
821
|
-
const histogramLines = generateHistogram(stats.timings, 8, 30);
|
|
822
|
-
for (const line of histogramLines) {
|
|
823
|
-
console.log(` ${this.color(line, 'dim')}`);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
private formatLatency(ms: number): string {
|
|
829
|
-
if (ms < 1) {
|
|
830
|
-
return `${(ms * 1000).toFixed(0)}µs`;
|
|
831
|
-
}
|
|
832
|
-
if (ms < 1000) {
|
|
833
|
-
return `${ms.toFixed(1)}ms`;
|
|
834
|
-
}
|
|
835
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
logProfileSummary(results: ProfileResult[]): void {
|
|
839
|
-
if (!this.shouldShowOutput()) {
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const totalIterations = results.reduce((sum, r) => sum + r.stats.iterations, 0);
|
|
844
|
-
const totalFailures = results.reduce((sum, r) => sum + r.stats.failures, 0);
|
|
845
|
-
|
|
846
|
-
console.log();
|
|
847
|
-
console.log(this.color('─'.repeat(50), 'dim'));
|
|
848
|
-
console.log(
|
|
849
|
-
`${this.color('⚡ Profile Summary:', 'magenta')} ${results.length} request${results.length === 1 ? '' : 's'}, ${totalIterations} total iterations`,
|
|
850
|
-
);
|
|
851
|
-
|
|
852
|
-
if (totalFailures > 0) {
|
|
853
|
-
console.log(this.color(` ${totalFailures} total failures`, 'yellow'));
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|