modestbench 0.0.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.
Files changed (275) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE.md +55 -0
  3. package/README.md +699 -0
  4. package/dist/bootstrap.cjs +37 -0
  5. package/dist/bootstrap.cjs.map +1 -0
  6. package/dist/bootstrap.d.cts +17 -0
  7. package/dist/bootstrap.d.cts.map +1 -0
  8. package/dist/bootstrap.d.ts +17 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +33 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli/commands/history.cjs +459 -0
  13. package/dist/cli/commands/history.cjs.map +1 -0
  14. package/dist/cli/commands/history.d.cts +34 -0
  15. package/dist/cli/commands/history.d.cts.map +1 -0
  16. package/dist/cli/commands/history.d.ts +34 -0
  17. package/dist/cli/commands/history.d.ts.map +1 -0
  18. package/dist/cli/commands/history.js +422 -0
  19. package/dist/cli/commands/history.js.map +1 -0
  20. package/dist/cli/commands/init.cjs +566 -0
  21. package/dist/cli/commands/init.cjs.map +1 -0
  22. package/dist/cli/commands/init.d.cts +26 -0
  23. package/dist/cli/commands/init.d.cts.map +1 -0
  24. package/dist/cli/commands/init.d.ts +26 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -0
  26. package/dist/cli/commands/init.js +562 -0
  27. package/dist/cli/commands/init.js.map +1 -0
  28. package/dist/cli/commands/run.cjs +285 -0
  29. package/dist/cli/commands/run.cjs.map +1 -0
  30. package/dist/cli/commands/run.d.cts +37 -0
  31. package/dist/cli/commands/run.d.cts.map +1 -0
  32. package/dist/cli/commands/run.d.ts +37 -0
  33. package/dist/cli/commands/run.d.ts.map +1 -0
  34. package/dist/cli/commands/run.js +248 -0
  35. package/dist/cli/commands/run.js.map +1 -0
  36. package/dist/cli/index.cjs +523 -0
  37. package/dist/cli/index.cjs.map +1 -0
  38. package/dist/cli/index.d.cts +58 -0
  39. package/dist/cli/index.d.cts.map +1 -0
  40. package/dist/cli/index.d.ts +58 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +515 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/config/manager.cjs +370 -0
  45. package/dist/config/manager.cjs.map +1 -0
  46. package/dist/config/manager.d.cts +46 -0
  47. package/dist/config/manager.d.cts.map +1 -0
  48. package/dist/config/manager.d.ts +46 -0
  49. package/dist/config/manager.d.ts.map +1 -0
  50. package/dist/config/manager.js +333 -0
  51. package/dist/config/manager.js.map +1 -0
  52. package/dist/config/schema.cjs +182 -0
  53. package/dist/config/schema.cjs.map +1 -0
  54. package/dist/config/schema.d.cts +51 -0
  55. package/dist/config/schema.d.cts.map +1 -0
  56. package/dist/config/schema.d.ts +51 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +145 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/constants.cjs +22 -0
  61. package/dist/constants.cjs.map +1 -0
  62. package/dist/constants.d.cts +10 -0
  63. package/dist/constants.d.cts.map +1 -0
  64. package/dist/constants.d.ts +10 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +19 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/core/benchmark-schema.cjs +135 -0
  69. package/dist/core/benchmark-schema.cjs.map +1 -0
  70. package/dist/core/benchmark-schema.d.cts +139 -0
  71. package/dist/core/benchmark-schema.d.cts.map +1 -0
  72. package/dist/core/benchmark-schema.d.ts +139 -0
  73. package/dist/core/benchmark-schema.d.ts.map +1 -0
  74. package/dist/core/benchmark-schema.js +132 -0
  75. package/dist/core/benchmark-schema.js.map +1 -0
  76. package/dist/core/engine.cjs +669 -0
  77. package/dist/core/engine.cjs.map +1 -0
  78. package/dist/core/engine.d.cts +128 -0
  79. package/dist/core/engine.d.cts.map +1 -0
  80. package/dist/core/engine.d.ts +128 -0
  81. package/dist/core/engine.d.ts.map +1 -0
  82. package/dist/core/engine.js +632 -0
  83. package/dist/core/engine.js.map +1 -0
  84. package/dist/core/engines/accurate-engine.cjs +292 -0
  85. package/dist/core/engines/accurate-engine.cjs.map +1 -0
  86. package/dist/core/engines/accurate-engine.d.cts +63 -0
  87. package/dist/core/engines/accurate-engine.d.cts.map +1 -0
  88. package/dist/core/engines/accurate-engine.d.ts +63 -0
  89. package/dist/core/engines/accurate-engine.d.ts.map +1 -0
  90. package/dist/core/engines/accurate-engine.js +288 -0
  91. package/dist/core/engines/accurate-engine.js.map +1 -0
  92. package/dist/core/engines/index.cjs +21 -0
  93. package/dist/core/engines/index.cjs.map +1 -0
  94. package/dist/core/engines/index.d.cts +16 -0
  95. package/dist/core/engines/index.d.cts.map +1 -0
  96. package/dist/core/engines/index.d.ts +16 -0
  97. package/dist/core/engines/index.d.ts.map +1 -0
  98. package/dist/core/engines/index.js +16 -0
  99. package/dist/core/engines/index.js.map +1 -0
  100. package/dist/core/engines/tinybench-engine.cjs +286 -0
  101. package/dist/core/engines/tinybench-engine.cjs.map +1 -0
  102. package/dist/core/engines/tinybench-engine.d.cts +18 -0
  103. package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
  104. package/dist/core/engines/tinybench-engine.d.ts +18 -0
  105. package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
  106. package/dist/core/engines/tinybench-engine.js +282 -0
  107. package/dist/core/engines/tinybench-engine.js.map +1 -0
  108. package/dist/core/error-manager.cjs +303 -0
  109. package/dist/core/error-manager.cjs.map +1 -0
  110. package/dist/core/error-manager.d.cts +77 -0
  111. package/dist/core/error-manager.d.cts.map +1 -0
  112. package/dist/core/error-manager.d.ts +77 -0
  113. package/dist/core/error-manager.d.ts.map +1 -0
  114. package/dist/core/error-manager.js +299 -0
  115. package/dist/core/error-manager.js.map +1 -0
  116. package/dist/core/loader.cjs +287 -0
  117. package/dist/core/loader.cjs.map +1 -0
  118. package/dist/core/loader.d.cts +55 -0
  119. package/dist/core/loader.d.cts.map +1 -0
  120. package/dist/core/loader.d.ts +55 -0
  121. package/dist/core/loader.d.ts.map +1 -0
  122. package/dist/core/loader.js +250 -0
  123. package/dist/core/loader.js.map +1 -0
  124. package/dist/core/stats-utils.cjs +99 -0
  125. package/dist/core/stats-utils.cjs.map +1 -0
  126. package/dist/core/stats-utils.d.cts +50 -0
  127. package/dist/core/stats-utils.d.cts.map +1 -0
  128. package/dist/core/stats-utils.d.ts +50 -0
  129. package/dist/core/stats-utils.d.ts.map +1 -0
  130. package/dist/core/stats-utils.js +94 -0
  131. package/dist/core/stats-utils.js.map +1 -0
  132. package/dist/index.cjs +64 -0
  133. package/dist/index.cjs.map +1 -0
  134. package/dist/index.d.cts +22 -0
  135. package/dist/index.d.cts.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +30 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/progress/manager.cjs +325 -0
  141. package/dist/progress/manager.cjs.map +1 -0
  142. package/dist/progress/manager.d.cts +125 -0
  143. package/dist/progress/manager.d.cts.map +1 -0
  144. package/dist/progress/manager.d.ts +125 -0
  145. package/dist/progress/manager.d.ts.map +1 -0
  146. package/dist/progress/manager.js +321 -0
  147. package/dist/progress/manager.js.map +1 -0
  148. package/dist/reporters/csv.cjs +250 -0
  149. package/dist/reporters/csv.cjs.map +1 -0
  150. package/dist/reporters/csv.d.cts +92 -0
  151. package/dist/reporters/csv.d.cts.map +1 -0
  152. package/dist/reporters/csv.d.ts +92 -0
  153. package/dist/reporters/csv.d.ts.map +1 -0
  154. package/dist/reporters/csv.js +246 -0
  155. package/dist/reporters/csv.js.map +1 -0
  156. package/dist/reporters/human.cjs +516 -0
  157. package/dist/reporters/human.cjs.map +1 -0
  158. package/dist/reporters/human.d.cts +86 -0
  159. package/dist/reporters/human.d.cts.map +1 -0
  160. package/dist/reporters/human.d.ts +86 -0
  161. package/dist/reporters/human.d.ts.map +1 -0
  162. package/dist/reporters/human.js +509 -0
  163. package/dist/reporters/human.js.map +1 -0
  164. package/dist/reporters/index.cjs +17 -0
  165. package/dist/reporters/index.cjs.map +1 -0
  166. package/dist/reporters/index.d.cts +10 -0
  167. package/dist/reporters/index.d.cts.map +1 -0
  168. package/dist/reporters/index.d.ts +10 -0
  169. package/dist/reporters/index.d.ts.map +1 -0
  170. package/dist/reporters/index.js +10 -0
  171. package/dist/reporters/index.js.map +1 -0
  172. package/dist/reporters/json.cjs +215 -0
  173. package/dist/reporters/json.cjs.map +1 -0
  174. package/dist/reporters/json.d.cts +79 -0
  175. package/dist/reporters/json.d.cts.map +1 -0
  176. package/dist/reporters/json.d.ts +79 -0
  177. package/dist/reporters/json.d.ts.map +1 -0
  178. package/dist/reporters/json.js +211 -0
  179. package/dist/reporters/json.js.map +1 -0
  180. package/dist/reporters/registry.cjs +255 -0
  181. package/dist/reporters/registry.cjs.map +1 -0
  182. package/dist/reporters/registry.d.cts +155 -0
  183. package/dist/reporters/registry.d.cts.map +1 -0
  184. package/dist/reporters/registry.d.ts +155 -0
  185. package/dist/reporters/registry.d.ts.map +1 -0
  186. package/dist/reporters/registry.js +249 -0
  187. package/dist/reporters/registry.js.map +1 -0
  188. package/dist/reporters/simple.cjs +328 -0
  189. package/dist/reporters/simple.cjs.map +1 -0
  190. package/dist/reporters/simple.d.cts +51 -0
  191. package/dist/reporters/simple.d.cts.map +1 -0
  192. package/dist/reporters/simple.d.ts +51 -0
  193. package/dist/reporters/simple.d.ts.map +1 -0
  194. package/dist/reporters/simple.js +321 -0
  195. package/dist/reporters/simple.js.map +1 -0
  196. package/dist/schema/modestbench-config.schema.json +162 -0
  197. package/dist/storage/history.cjs +456 -0
  198. package/dist/storage/history.cjs.map +1 -0
  199. package/dist/storage/history.d.cts +99 -0
  200. package/dist/storage/history.d.cts.map +1 -0
  201. package/dist/storage/history.d.ts +99 -0
  202. package/dist/storage/history.d.ts.map +1 -0
  203. package/dist/storage/history.js +452 -0
  204. package/dist/storage/history.js.map +1 -0
  205. package/dist/types/cli.cjs +21 -0
  206. package/dist/types/cli.cjs.map +1 -0
  207. package/dist/types/cli.d.cts +296 -0
  208. package/dist/types/cli.d.cts.map +1 -0
  209. package/dist/types/cli.d.ts +296 -0
  210. package/dist/types/cli.d.ts.map +1 -0
  211. package/dist/types/cli.js +18 -0
  212. package/dist/types/cli.js.map +1 -0
  213. package/dist/types/core.cjs +14 -0
  214. package/dist/types/core.cjs.map +1 -0
  215. package/dist/types/core.d.cts +380 -0
  216. package/dist/types/core.d.cts.map +1 -0
  217. package/dist/types/core.d.ts +380 -0
  218. package/dist/types/core.d.ts.map +1 -0
  219. package/dist/types/core.js +13 -0
  220. package/dist/types/core.js.map +1 -0
  221. package/dist/types/index.cjs +27 -0
  222. package/dist/types/index.cjs.map +1 -0
  223. package/dist/types/index.d.cts +11 -0
  224. package/dist/types/index.d.cts.map +1 -0
  225. package/dist/types/index.d.ts +11 -0
  226. package/dist/types/index.d.ts.map +1 -0
  227. package/dist/types/index.js +11 -0
  228. package/dist/types/index.js.map +1 -0
  229. package/dist/types/interfaces.cjs +10 -0
  230. package/dist/types/interfaces.cjs.map +1 -0
  231. package/dist/types/interfaces.d.cts +381 -0
  232. package/dist/types/interfaces.d.cts.map +1 -0
  233. package/dist/types/interfaces.d.ts +381 -0
  234. package/dist/types/interfaces.d.ts.map +1 -0
  235. package/dist/types/interfaces.js +9 -0
  236. package/dist/types/interfaces.js.map +1 -0
  237. package/dist/types/utility.cjs +92 -0
  238. package/dist/types/utility.cjs.map +1 -0
  239. package/dist/types/utility.d.cts +330 -0
  240. package/dist/types/utility.d.cts.map +1 -0
  241. package/dist/types/utility.d.ts +330 -0
  242. package/dist/types/utility.d.ts.map +1 -0
  243. package/dist/types/utility.js +78 -0
  244. package/dist/types/utility.js.map +1 -0
  245. package/package.json +211 -0
  246. package/src/bootstrap.ts +35 -0
  247. package/src/cli/commands/history.ts +569 -0
  248. package/src/cli/commands/init.ts +658 -0
  249. package/src/cli/commands/run.ts +346 -0
  250. package/src/cli/index.ts +642 -0
  251. package/src/config/manager.ts +387 -0
  252. package/src/config/schema.ts +188 -0
  253. package/src/constants.ts +21 -0
  254. package/src/core/benchmark-schema.ts +185 -0
  255. package/src/core/engine.ts +888 -0
  256. package/src/core/engines/accurate-engine.ts +408 -0
  257. package/src/core/engines/index.ts +16 -0
  258. package/src/core/engines/tinybench-engine.ts +335 -0
  259. package/src/core/error-manager.ts +372 -0
  260. package/src/core/loader.ts +324 -0
  261. package/src/core/stats-utils.ts +135 -0
  262. package/src/index.ts +46 -0
  263. package/src/progress/manager.ts +415 -0
  264. package/src/reporters/csv.ts +368 -0
  265. package/src/reporters/human.ts +707 -0
  266. package/src/reporters/index.ts +10 -0
  267. package/src/reporters/json.ts +302 -0
  268. package/src/reporters/registry.ts +349 -0
  269. package/src/reporters/simple.ts +459 -0
  270. package/src/storage/history.ts +600 -0
  271. package/src/types/cli.ts +312 -0
  272. package/src/types/core.ts +414 -0
  273. package/src/types/index.ts +18 -0
  274. package/src/types/interfaces.ts +451 -0
  275. package/src/types/utility.ts +446 -0
@@ -0,0 +1,368 @@
1
+ /**
2
+ * ModestBench CSV Reporter
3
+ *
4
+ * Outputs benchmark results in CSV format for data analysis and visualization.
5
+ * Provides structured tabular data suitable for spreadsheets and statistical
6
+ * tools.
7
+ */
8
+
9
+ import { mkdirSync, writeFileSync } from 'node:fs';
10
+ import { dirname } from 'node:path';
11
+
12
+ import type {
13
+ BenchmarkRun,
14
+ FileResult,
15
+ ProgressState,
16
+ SuiteResult,
17
+ TaskResult,
18
+ } from '../types/index.js';
19
+
20
+ import { BaseReporter } from './registry.js';
21
+
22
+ /**
23
+ * CSV column definitions for task results
24
+ */
25
+ interface CsvRow {
26
+ readonly arch: string;
27
+ readonly ciProvider?: string | undefined;
28
+ readonly cpuCores: number;
29
+ readonly cpuModel: string;
30
+ readonly error?: string | undefined;
31
+ readonly file: string;
32
+ readonly gitBranch?: string | undefined;
33
+ readonly gitCommit?: string | undefined;
34
+ readonly iterations: number;
35
+ readonly marginOfError: number;
36
+ readonly max: number;
37
+ readonly mean: number;
38
+ readonly min: number;
39
+ readonly nodeVersion: string;
40
+ readonly opsPerSecond: number;
41
+ readonly p95: number;
42
+ readonly p99: number;
43
+ readonly platform: string;
44
+ readonly stdDev: number;
45
+ readonly suite: string;
46
+ readonly task: string;
47
+ readonly timestamp: string;
48
+ readonly totalMemory: number;
49
+ readonly variance: number;
50
+ }
51
+
52
+ /**
53
+ * CSV reporter for structured tabular output
54
+ */
55
+ export class CsvReporter extends BaseReporter {
56
+ private currentFile = '';
57
+
58
+ private currentRun?: BenchmarkRun;
59
+
60
+ private currentSuite = '';
61
+
62
+ private readonly delimiter: string;
63
+
64
+ private readonly includeHeaders: boolean;
65
+
66
+ private readonly includeMetadata: boolean;
67
+
68
+ private readonly outputPath?: string | undefined;
69
+
70
+ private readonly quiet: boolean;
71
+
72
+ private readonly quote: string;
73
+
74
+ private rows: CsvRow[] = [];
75
+
76
+ constructor(
77
+ options: {
78
+ delimiter?: string;
79
+ includeHeaders?: boolean;
80
+ includeMetadata?: boolean;
81
+ outputPath?: string;
82
+ quiet?: boolean;
83
+ quote?: string;
84
+ verbose?: boolean;
85
+ } = {},
86
+ ) {
87
+ super('csv', options);
88
+
89
+ this.outputPath = options.outputPath;
90
+ this.includeHeaders = options.includeHeaders ?? true;
91
+ this.includeMetadata = options.includeMetadata ?? true;
92
+ this.delimiter = options.delimiter ?? ',';
93
+ this.quote = options.quote ?? '"';
94
+ this.quiet = options.quiet ?? false;
95
+ }
96
+
97
+ /**
98
+ * Check if headers are included
99
+ */
100
+ areHeadersIncluded(): boolean {
101
+ return this.includeHeaders;
102
+ }
103
+
104
+ /**
105
+ * Get the delimiter character
106
+ */
107
+ getDelimiter(): string {
108
+ return this.delimiter;
109
+ }
110
+
111
+ /**
112
+ * Get the output path (if configured)
113
+ */
114
+ getOutputPath(): string | undefined {
115
+ return this.outputPath;
116
+ }
117
+
118
+ /**
119
+ * Get the quote character
120
+ */
121
+ getQuote(): string {
122
+ return this.quote;
123
+ }
124
+
125
+ /**
126
+ * Get the number of rows collected
127
+ */
128
+ getRowCount(): number {
129
+ return this.rows.length;
130
+ }
131
+
132
+ /**
133
+ * Check if metadata is included
134
+ */
135
+ isMetadataIncluded(): boolean {
136
+ return this.includeMetadata;
137
+ }
138
+
139
+ async onEnd(_run: BenchmarkRun): Promise<void> {
140
+ const csvContent = this.generateCsv();
141
+
142
+ if (this.outputPath) {
143
+ await this.writeToFile(csvContent);
144
+ } else {
145
+ this.writeToStdout(csvContent);
146
+ }
147
+ }
148
+
149
+ onError(error: Error): void {
150
+ console.error('CSV Reporter Error:', error.message);
151
+ }
152
+
153
+ onFileEnd(_result: FileResult): void {
154
+ // No-op for CSV reporter
155
+ }
156
+
157
+ onFileStart(file: string): void {
158
+ this.currentFile = file;
159
+ }
160
+
161
+ onProgress(_state: ProgressState): void {
162
+ // No-op for CSV reporter
163
+ }
164
+
165
+ onStart(run: BenchmarkRun): void {
166
+ this.currentRun = run;
167
+ this.rows = [];
168
+ }
169
+
170
+ onSuiteEnd(_result: SuiteResult): void {
171
+ // No-op for CSV reporter
172
+ }
173
+
174
+ onSuiteStart(suite: string): void {
175
+ this.currentSuite = suite;
176
+ }
177
+
178
+ onTaskResult(result: TaskResult): void {
179
+ if (!this.currentRun) {
180
+ return;
181
+ }
182
+
183
+ const row: CsvRow = {
184
+ arch: this.currentRun.environment.arch,
185
+ ciProvider: this.currentRun.ci?.provider,
186
+ cpuCores: this.currentRun.environment.cpu.cores,
187
+ cpuModel: this.currentRun.environment.cpu.model,
188
+ error: result.error?.message,
189
+ file: this.currentFile,
190
+ gitBranch: this.currentRun.git?.branch,
191
+ gitCommit: this.currentRun.git?.commit,
192
+ iterations: result.iterations,
193
+ marginOfError: result.marginOfError,
194
+ max: result.max,
195
+ mean: result.mean,
196
+ min: result.min,
197
+ nodeVersion: this.currentRun.environment.nodeVersion,
198
+ opsPerSecond: result.opsPerSecond,
199
+ p95: result.p95,
200
+ p99: result.p99,
201
+ platform: this.currentRun.environment.platform,
202
+ stdDev: result.stdDev,
203
+ suite: this.currentSuite,
204
+ task: result.name,
205
+ timestamp: new Date().toISOString(),
206
+ totalMemory: this.currentRun.environment.memory.total,
207
+ variance: result.variance,
208
+ };
209
+
210
+ this.rows.push(row);
211
+ }
212
+
213
+ onTaskStart(_task: string): void {
214
+ // No-op for CSV reporter
215
+ }
216
+
217
+ /**
218
+ * Escape a field value for CSV format
219
+ */
220
+ private escapeField(value: string): string {
221
+ // If value contains delimiter, quote, or newline, wrap in quotes
222
+ if (
223
+ value.includes(this.delimiter) ||
224
+ value.includes(this.quote) ||
225
+ value.includes('\n') ||
226
+ value.includes('\r')
227
+ ) {
228
+ // Escape any existing quotes by doubling them
229
+ const escaped = value.replace(
230
+ new RegExp(this.quote, 'g'),
231
+ this.quote + this.quote,
232
+ );
233
+ return this.quote + escaped + this.quote;
234
+ }
235
+
236
+ return value;
237
+ }
238
+
239
+ /**
240
+ * Generate CSV content from collected rows
241
+ */
242
+ private generateCsv(): string {
243
+ if (this.rows.length === 0) {
244
+ return this.includeHeaders ? this.generateHeaders() : '';
245
+ }
246
+
247
+ const lines: string[] = [];
248
+
249
+ if (this.includeHeaders) {
250
+ lines.push(this.generateHeaders());
251
+ }
252
+
253
+ for (const row of this.rows) {
254
+ lines.push(this.generateRow(row));
255
+ }
256
+
257
+ return lines.join('\n') + '\n';
258
+ }
259
+
260
+ /**
261
+ * Generate CSV headers
262
+ */
263
+ private generateHeaders(): string {
264
+ const headers = [
265
+ 'file',
266
+ 'suite',
267
+ 'task',
268
+ 'mean',
269
+ 'stdDev',
270
+ 'min',
271
+ 'max',
272
+ 'iterations',
273
+ 'opsPerSecond',
274
+ 'marginOfError',
275
+ 'variance',
276
+ 'p95',
277
+ 'p99',
278
+ 'error',
279
+ 'timestamp',
280
+ ];
281
+
282
+ if (this.includeMetadata) {
283
+ headers.push(
284
+ 'nodeVersion',
285
+ 'platform',
286
+ 'arch',
287
+ 'cpuModel',
288
+ 'cpuCores',
289
+ 'totalMemory',
290
+ 'gitCommit',
291
+ 'gitBranch',
292
+ 'ciProvider',
293
+ );
294
+ }
295
+
296
+ return headers.map((h) => this.escapeField(h)).join(this.delimiter);
297
+ }
298
+
299
+ /**
300
+ * Generate a CSV row from a data row
301
+ */
302
+ private generateRow(row: CsvRow): string {
303
+ const values = [
304
+ row.file || '',
305
+ row.suite || '',
306
+ row.task || '',
307
+ (row.mean ?? 0).toString(),
308
+ (row.stdDev ?? 0).toString(),
309
+ (row.min ?? 0).toString(),
310
+ (row.max ?? 0).toString(),
311
+ (row.iterations ?? 0).toString(),
312
+ (row.opsPerSecond ?? 0).toString(),
313
+ (row.marginOfError ?? 0).toString(),
314
+ (row.variance ?? 0).toString(),
315
+ (row.p95 ?? 0).toString(),
316
+ (row.p99 ?? 0).toString(),
317
+ row.error || '',
318
+ row.timestamp || '',
319
+ ];
320
+
321
+ if (this.includeMetadata) {
322
+ values.push(
323
+ row.nodeVersion || '',
324
+ row.platform || '',
325
+ row.arch || '',
326
+ row.cpuModel || '',
327
+ (row.cpuCores ?? 0).toString(),
328
+ (row.totalMemory ?? 0).toString(),
329
+ row.gitCommit || '',
330
+ row.gitBranch || '',
331
+ row.ciProvider || '',
332
+ );
333
+ }
334
+
335
+ return values.map((v) => this.escapeField(v)).join(this.delimiter);
336
+ }
337
+
338
+ /**
339
+ * Write CSV content to file
340
+ */
341
+ private async writeToFile(csvContent: string): Promise<void> {
342
+ if (!this.outputPath) {
343
+ throw new Error('Output path not specified');
344
+ }
345
+
346
+ try {
347
+ // Ensure directory exists
348
+ const dir = dirname(this.outputPath);
349
+ mkdirSync(dir, { recursive: true });
350
+
351
+ // Write CSV file
352
+ writeFileSync(this.outputPath, csvContent, 'utf8');
353
+ } catch (error) {
354
+ throw new Error(
355
+ `Failed to write CSV output to ${this.outputPath}: ${error instanceof Error ? error.message : String(error)}`,
356
+ );
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Write CSV content to stdout
362
+ */
363
+ private writeToStdout(csvContent: string): void {
364
+ // Always write to stdout when no output path is specified
365
+ // The quiet flag only affects progress messages (stderr), not data output
366
+ console.log(csvContent);
367
+ }
368
+ }