modestbench 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +162 -57
  3. package/dist/bootstrap.cjs +10 -10
  4. package/dist/bootstrap.cjs.map +1 -1
  5. package/dist/bootstrap.d.cts.map +1 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +5 -5
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/cli/commands/analyze.cjs +60 -0
  10. package/dist/cli/commands/analyze.cjs.map +1 -0
  11. package/dist/cli/commands/analyze.d.cts +35 -0
  12. package/dist/cli/commands/analyze.d.cts.map +1 -0
  13. package/dist/cli/commands/analyze.d.ts +35 -0
  14. package/dist/cli/commands/analyze.d.ts.map +1 -0
  15. package/dist/cli/commands/analyze.js +56 -0
  16. package/dist/cli/commands/analyze.js.map +1 -0
  17. package/dist/cli/commands/baseline.cjs +404 -0
  18. package/dist/cli/commands/baseline.cjs.map +1 -0
  19. package/dist/cli/commands/baseline.d.cts +72 -0
  20. package/dist/cli/commands/baseline.d.cts.map +1 -0
  21. package/dist/cli/commands/baseline.d.ts +72 -0
  22. package/dist/cli/commands/baseline.d.ts.map +1 -0
  23. package/dist/cli/commands/baseline.js +396 -0
  24. package/dist/cli/commands/baseline.js.map +1 -0
  25. package/dist/cli/commands/history.cjs +108 -266
  26. package/dist/cli/commands/history.cjs.map +1 -1
  27. package/dist/cli/commands/history.d.cts +75 -12
  28. package/dist/cli/commands/history.d.cts.map +1 -1
  29. package/dist/cli/commands/history.d.ts +75 -12
  30. package/dist/cli/commands/history.d.ts.map +1 -1
  31. package/dist/cli/commands/history.js +105 -268
  32. package/dist/cli/commands/history.js.map +1 -1
  33. package/dist/cli/commands/init.cjs +88 -155
  34. package/dist/cli/commands/init.cjs.map +1 -1
  35. package/dist/cli/commands/init.d.cts +4 -4
  36. package/dist/cli/commands/init.d.cts.map +1 -1
  37. package/dist/cli/commands/init.d.ts +4 -4
  38. package/dist/cli/commands/init.d.ts.map +1 -1
  39. package/dist/cli/commands/init.js +88 -155
  40. package/dist/cli/commands/init.js.map +1 -1
  41. package/dist/cli/commands/run.cjs +143 -112
  42. package/dist/cli/commands/run.cjs.map +1 -1
  43. package/dist/cli/commands/run.d.cts +17 -3
  44. package/dist/cli/commands/run.d.cts.map +1 -1
  45. package/dist/cli/commands/run.d.ts +17 -3
  46. package/dist/cli/commands/run.d.ts.map +1 -1
  47. package/dist/cli/commands/run.js +142 -78
  48. package/dist/cli/commands/run.js.map +1 -1
  49. package/dist/cli/index.cjs +671 -266
  50. package/dist/cli/index.cjs.map +1 -1
  51. package/dist/cli/index.d.cts +4 -16
  52. package/dist/cli/index.d.cts.map +1 -1
  53. package/dist/cli/index.d.ts +4 -16
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +664 -259
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/config/budget-schema.cjs +172 -0
  58. package/dist/config/budget-schema.cjs.map +1 -0
  59. package/dist/config/budget-schema.d.cts +59 -0
  60. package/dist/config/budget-schema.d.cts.map +1 -0
  61. package/dist/config/budget-schema.d.ts +59 -0
  62. package/dist/config/budget-schema.d.ts.map +1 -0
  63. package/dist/config/budget-schema.js +166 -0
  64. package/dist/config/budget-schema.js.map +1 -0
  65. package/dist/config/schema.cjs +182 -2
  66. package/dist/config/schema.cjs.map +1 -1
  67. package/dist/config/schema.d.cts +122 -3
  68. package/dist/config/schema.d.cts.map +1 -1
  69. package/dist/config/schema.d.ts +122 -3
  70. package/dist/config/schema.d.ts.map +1 -1
  71. package/dist/config/schema.js +180 -1
  72. package/dist/config/schema.js.map +1 -1
  73. package/dist/constants.cjs +45 -2
  74. package/dist/constants.cjs.map +1 -1
  75. package/dist/constants.d.cts +41 -0
  76. package/dist/constants.d.cts.map +1 -1
  77. package/dist/constants.d.ts +41 -0
  78. package/dist/constants.d.ts.map +1 -1
  79. package/dist/constants.js +44 -1
  80. package/dist/constants.js.map +1 -1
  81. package/dist/core/engine.cjs +104 -15
  82. package/dist/core/engine.cjs.map +1 -1
  83. package/dist/core/engine.d.cts +7 -4
  84. package/dist/core/engine.d.cts.map +1 -1
  85. package/dist/core/engine.d.ts +7 -4
  86. package/dist/core/engine.d.ts.map +1 -1
  87. package/dist/core/engine.js +105 -16
  88. package/dist/core/engine.js.map +1 -1
  89. package/dist/core/output-path-resolver.cjs +41 -0
  90. package/dist/core/output-path-resolver.cjs.map +1 -0
  91. package/dist/core/output-path-resolver.d.cts +10 -0
  92. package/dist/core/output-path-resolver.d.cts.map +1 -0
  93. package/dist/core/output-path-resolver.d.ts +10 -0
  94. package/dist/core/output-path-resolver.d.ts.map +1 -0
  95. package/dist/core/output-path-resolver.js +37 -0
  96. package/dist/core/output-path-resolver.js.map +1 -0
  97. package/dist/errors/base.cjs +12 -3
  98. package/dist/errors/base.cjs.map +1 -1
  99. package/dist/errors/base.d.cts +7 -0
  100. package/dist/errors/base.d.cts.map +1 -1
  101. package/dist/errors/base.d.ts +7 -0
  102. package/dist/errors/base.d.ts.map +1 -1
  103. package/dist/errors/base.js +10 -2
  104. package/dist/errors/base.js.map +1 -1
  105. package/dist/errors/budget.cjs +37 -0
  106. package/dist/errors/budget.cjs.map +1 -0
  107. package/dist/errors/budget.d.cts +31 -0
  108. package/dist/errors/budget.d.cts.map +1 -0
  109. package/dist/errors/budget.d.ts +31 -0
  110. package/dist/errors/budget.d.ts.map +1 -0
  111. package/dist/errors/budget.js +33 -0
  112. package/dist/errors/budget.js.map +1 -0
  113. package/dist/errors/index.cjs +4 -1
  114. package/dist/errors/index.cjs.map +1 -1
  115. package/dist/errors/index.d.cts +1 -0
  116. package/dist/errors/index.d.cts.map +1 -1
  117. package/dist/errors/index.d.ts +1 -0
  118. package/dist/errors/index.d.ts.map +1 -1
  119. package/dist/errors/index.js +2 -0
  120. package/dist/errors/index.js.map +1 -1
  121. package/dist/formatters/history/base.cjs +9 -0
  122. package/dist/formatters/history/base.cjs.map +1 -0
  123. package/dist/formatters/history/base.d.cts +26 -0
  124. package/dist/formatters/history/base.d.cts.map +1 -0
  125. package/dist/formatters/history/base.d.ts +26 -0
  126. package/dist/formatters/history/base.d.ts.map +1 -0
  127. package/dist/formatters/history/base.js +8 -0
  128. package/dist/formatters/history/base.js.map +1 -0
  129. package/dist/formatters/history/compare.cjs +127 -0
  130. package/dist/formatters/history/compare.cjs.map +1 -0
  131. package/dist/formatters/history/compare.d.cts +21 -0
  132. package/dist/formatters/history/compare.d.cts.map +1 -0
  133. package/dist/formatters/history/compare.d.ts +21 -0
  134. package/dist/formatters/history/compare.d.ts.map +1 -0
  135. package/dist/formatters/history/compare.js +123 -0
  136. package/dist/formatters/history/compare.js.map +1 -0
  137. package/dist/formatters/history/list.cjs +74 -0
  138. package/dist/formatters/history/list.cjs.map +1 -0
  139. package/dist/formatters/history/list.d.cts +25 -0
  140. package/dist/formatters/history/list.d.cts.map +1 -0
  141. package/dist/formatters/history/list.d.ts +25 -0
  142. package/dist/formatters/history/list.d.ts.map +1 -0
  143. package/dist/formatters/history/list.js +70 -0
  144. package/dist/formatters/history/list.js.map +1 -0
  145. package/dist/formatters/history/show.cjs +98 -0
  146. package/dist/formatters/history/show.cjs.map +1 -0
  147. package/dist/formatters/history/show.d.cts +21 -0
  148. package/dist/formatters/history/show.d.cts.map +1 -0
  149. package/dist/formatters/history/show.d.ts +21 -0
  150. package/dist/formatters/history/show.d.ts.map +1 -0
  151. package/dist/formatters/history/show.js +94 -0
  152. package/dist/formatters/history/show.js.map +1 -0
  153. package/dist/formatters/history/trends.cjs +194 -0
  154. package/dist/formatters/history/trends.cjs.map +1 -0
  155. package/dist/formatters/history/trends.d.cts +22 -0
  156. package/dist/formatters/history/trends.d.cts.map +1 -0
  157. package/dist/formatters/history/trends.d.ts +22 -0
  158. package/dist/formatters/history/trends.d.ts.map +1 -0
  159. package/dist/formatters/history/trends.js +190 -0
  160. package/dist/formatters/history/trends.js.map +1 -0
  161. package/dist/formatters/history/visualization.cjs +79 -0
  162. package/dist/formatters/history/visualization.cjs.map +1 -0
  163. package/dist/formatters/history/visualization.d.cts +24 -0
  164. package/dist/formatters/history/visualization.d.cts.map +1 -0
  165. package/dist/formatters/history/visualization.d.ts +24 -0
  166. package/dist/formatters/history/visualization.d.ts.map +1 -0
  167. package/dist/formatters/history/visualization.js +74 -0
  168. package/dist/formatters/history/visualization.js.map +1 -0
  169. package/dist/index.cjs +27 -17
  170. package/dist/index.cjs.map +1 -1
  171. package/dist/index.d.cts +10 -5
  172. package/dist/index.d.cts.map +1 -1
  173. package/dist/index.d.ts +10 -5
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +14 -9
  176. package/dist/index.js.map +1 -1
  177. package/dist/reporters/csv.cjs +39 -19
  178. package/dist/reporters/csv.cjs.map +1 -1
  179. package/dist/reporters/csv.d.cts +4 -7
  180. package/dist/reporters/csv.d.cts.map +1 -1
  181. package/dist/reporters/csv.d.ts +4 -7
  182. package/dist/reporters/csv.d.ts.map +1 -1
  183. package/dist/reporters/csv.js +38 -18
  184. package/dist/reporters/csv.js.map +1 -1
  185. package/dist/reporters/human.cjs +87 -99
  186. package/dist/reporters/human.cjs.map +1 -1
  187. package/dist/reporters/human.d.cts +15 -14
  188. package/dist/reporters/human.d.cts.map +1 -1
  189. package/dist/reporters/human.d.ts +15 -14
  190. package/dist/reporters/human.d.ts.map +1 -1
  191. package/dist/reporters/human.js +68 -80
  192. package/dist/reporters/human.js.map +1 -1
  193. package/dist/reporters/json.cjs +25 -50
  194. package/dist/reporters/json.cjs.map +1 -1
  195. package/dist/reporters/json.d.cts +3 -29
  196. package/dist/reporters/json.d.cts.map +1 -1
  197. package/dist/reporters/json.d.ts +3 -29
  198. package/dist/reporters/json.d.ts.map +1 -1
  199. package/dist/reporters/json.js +26 -51
  200. package/dist/reporters/json.js.map +1 -1
  201. package/dist/reporters/profile-human.cjs +149 -0
  202. package/dist/reporters/profile-human.cjs.map +1 -0
  203. package/dist/reporters/profile-human.d.cts +44 -0
  204. package/dist/reporters/profile-human.d.cts.map +1 -0
  205. package/dist/reporters/profile-human.d.ts +44 -0
  206. package/dist/reporters/profile-human.d.ts.map +1 -0
  207. package/dist/reporters/profile-human.js +142 -0
  208. package/dist/reporters/profile-human.js.map +1 -0
  209. package/dist/reporters/simple.cjs +66 -46
  210. package/dist/reporters/simple.cjs.map +1 -1
  211. package/dist/reporters/simple.d.cts +15 -15
  212. package/dist/reporters/simple.d.cts.map +1 -1
  213. package/dist/reporters/simple.d.ts +15 -15
  214. package/dist/reporters/simple.d.ts.map +1 -1
  215. package/dist/reporters/simple.js +65 -45
  216. package/dist/reporters/simple.js.map +1 -1
  217. package/dist/schema/modestbench-config.schema.json +153 -0
  218. package/dist/services/baseline-storage.cjs +151 -0
  219. package/dist/services/baseline-storage.cjs.map +1 -0
  220. package/dist/services/baseline-storage.d.cts +55 -0
  221. package/dist/services/baseline-storage.d.cts.map +1 -0
  222. package/dist/services/baseline-storage.d.ts +55 -0
  223. package/dist/services/baseline-storage.d.ts.map +1 -0
  224. package/dist/services/baseline-storage.js +147 -0
  225. package/dist/services/baseline-storage.js.map +1 -0
  226. package/dist/services/budget-evaluator.cjs +146 -0
  227. package/dist/services/budget-evaluator.cjs.map +1 -0
  228. package/dist/services/budget-evaluator.d.cts +29 -0
  229. package/dist/services/budget-evaluator.d.cts.map +1 -0
  230. package/dist/services/budget-evaluator.d.ts +29 -0
  231. package/dist/services/budget-evaluator.d.ts.map +1 -0
  232. package/dist/services/budget-evaluator.js +142 -0
  233. package/dist/services/budget-evaluator.js.map +1 -0
  234. package/dist/{config/manager.cjs → services/config-manager.cjs} +25 -11
  235. package/dist/services/config-manager.cjs.map +1 -0
  236. package/dist/{config/manager.d.cts → services/config-manager.d.cts} +7 -2
  237. package/dist/services/config-manager.d.cts.map +1 -0
  238. package/dist/{config/manager.d.ts → services/config-manager.d.ts} +7 -2
  239. package/dist/services/config-manager.d.ts.map +1 -0
  240. package/dist/{config/manager.js → services/config-manager.js} +25 -11
  241. package/dist/services/config-manager.js.map +1 -0
  242. package/dist/{core/loader.cjs → services/file-loader.cjs} +5 -8
  243. package/dist/services/file-loader.cjs.map +1 -0
  244. package/dist/{core/loader.d.cts → services/file-loader.d.cts} +1 -1
  245. package/dist/services/file-loader.d.cts.map +1 -0
  246. package/dist/{core/loader.d.ts → services/file-loader.d.ts} +1 -1
  247. package/dist/services/file-loader.d.ts.map +1 -0
  248. package/dist/{core/loader.js → services/file-loader.js} +5 -8
  249. package/dist/services/file-loader.js.map +1 -0
  250. package/dist/services/history/comparison.cjs +124 -0
  251. package/dist/services/history/comparison.cjs.map +1 -0
  252. package/dist/services/history/comparison.d.cts +18 -0
  253. package/dist/services/history/comparison.d.cts.map +1 -0
  254. package/dist/services/history/comparison.d.ts +18 -0
  255. package/dist/services/history/comparison.d.ts.map +1 -0
  256. package/dist/services/history/comparison.js +120 -0
  257. package/dist/services/history/comparison.js.map +1 -0
  258. package/dist/services/history/models.cjs +9 -0
  259. package/dist/services/history/models.cjs.map +1 -0
  260. package/dist/services/history/models.d.cts +139 -0
  261. package/dist/services/history/models.d.cts.map +1 -0
  262. package/dist/services/history/models.d.ts +139 -0
  263. package/dist/services/history/models.d.ts.map +1 -0
  264. package/dist/services/history/models.js +8 -0
  265. package/dist/services/history/models.js.map +1 -0
  266. package/dist/services/history/query.cjs +97 -0
  267. package/dist/services/history/query.cjs.map +1 -0
  268. package/dist/services/history/query.d.cts +38 -0
  269. package/dist/services/history/query.d.cts.map +1 -0
  270. package/dist/services/history/query.d.ts +38 -0
  271. package/dist/services/history/query.d.ts.map +1 -0
  272. package/dist/services/history/query.js +92 -0
  273. package/dist/services/history/query.js.map +1 -0
  274. package/dist/services/history/trend-analysis.cjs +187 -0
  275. package/dist/services/history/trend-analysis.cjs.map +1 -0
  276. package/dist/services/history/trend-analysis.d.cts +34 -0
  277. package/dist/services/history/trend-analysis.d.cts.map +1 -0
  278. package/dist/services/history/trend-analysis.d.ts +34 -0
  279. package/dist/services/history/trend-analysis.d.ts.map +1 -0
  280. package/dist/services/history/trend-analysis.js +179 -0
  281. package/dist/services/history/trend-analysis.js.map +1 -0
  282. package/dist/{storage/history.cjs → services/history-storage.cjs} +1 -1
  283. package/dist/services/history-storage.cjs.map +1 -0
  284. package/dist/{storage/history.d.cts → services/history-storage.d.cts} +1 -1
  285. package/dist/services/history-storage.d.cts.map +1 -0
  286. package/dist/{storage/history.d.ts → services/history-storage.d.ts} +1 -1
  287. package/dist/services/history-storage.d.ts.map +1 -0
  288. package/dist/{storage/history.js → services/history-storage.js} +1 -1
  289. package/dist/services/history-storage.js.map +1 -0
  290. package/dist/services/profiler/profile-filter.cjs +113 -0
  291. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  292. package/dist/services/profiler/profile-filter.d.cts +20 -0
  293. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  294. package/dist/services/profiler/profile-filter.d.ts +20 -0
  295. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  296. package/dist/services/profiler/profile-filter.js +109 -0
  297. package/dist/services/profiler/profile-filter.js.map +1 -0
  298. package/dist/services/profiler/profile-parser.cjs +139 -0
  299. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  300. package/dist/services/profiler/profile-parser.d.cts +18 -0
  301. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  302. package/dist/services/profiler/profile-parser.d.ts +18 -0
  303. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  304. package/dist/services/profiler/profile-parser.js +132 -0
  305. package/dist/services/profiler/profile-parser.js.map +1 -0
  306. package/dist/services/profiler/profile-runner.cjs +90 -0
  307. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  308. package/dist/services/profiler/profile-runner.d.cts +29 -0
  309. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  310. package/dist/services/profiler/profile-runner.d.ts +29 -0
  311. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  312. package/dist/services/profiler/profile-runner.js +86 -0
  313. package/dist/services/profiler/profile-runner.js.map +1 -0
  314. package/dist/{progress/manager.cjs → services/progress-manager.cjs} +1 -1
  315. package/dist/services/progress-manager.cjs.map +1 -0
  316. package/dist/{progress/manager.d.cts → services/progress-manager.d.cts} +1 -1
  317. package/dist/services/progress-manager.d.cts.map +1 -0
  318. package/dist/{progress/manager.d.ts → services/progress-manager.d.ts} +1 -1
  319. package/dist/services/progress-manager.d.ts.map +1 -0
  320. package/dist/{progress/manager.js → services/progress-manager.js} +1 -1
  321. package/dist/services/progress-manager.js.map +1 -0
  322. package/dist/{reporters/registry.cjs → services/reporter-registry.cjs} +19 -25
  323. package/dist/services/reporter-registry.cjs.map +1 -0
  324. package/dist/{reporters/registry.d.cts → services/reporter-registry.d.cts} +19 -41
  325. package/dist/services/reporter-registry.d.cts.map +1 -0
  326. package/dist/{reporters/registry.d.ts → services/reporter-registry.d.ts} +19 -41
  327. package/dist/services/reporter-registry.d.ts.map +1 -0
  328. package/dist/{reporters/registry.js → services/reporter-registry.js} +19 -25
  329. package/dist/services/reporter-registry.js.map +1 -0
  330. package/dist/types/budgets.cjs +8 -0
  331. package/dist/types/budgets.cjs.map +1 -0
  332. package/dist/types/budgets.d.cts +149 -0
  333. package/dist/types/budgets.d.cts.map +1 -0
  334. package/dist/types/budgets.d.ts +149 -0
  335. package/dist/types/budgets.d.ts.map +1 -0
  336. package/dist/types/budgets.js +7 -0
  337. package/dist/types/budgets.js.map +1 -0
  338. package/dist/types/cli.cjs +2 -11
  339. package/dist/types/cli.cjs.map +1 -1
  340. package/dist/types/cli.d.cts +3 -224
  341. package/dist/types/cli.d.cts.map +1 -1
  342. package/dist/types/cli.d.ts +3 -224
  343. package/dist/types/cli.d.ts.map +1 -1
  344. package/dist/types/cli.js +2 -11
  345. package/dist/types/cli.js.map +1 -1
  346. package/dist/types/core.cjs +6 -1
  347. package/dist/types/core.cjs.map +1 -1
  348. package/dist/types/core.d.cts +13 -2
  349. package/dist/types/core.d.cts.map +1 -1
  350. package/dist/types/core.d.ts +13 -2
  351. package/dist/types/core.d.ts.map +1 -1
  352. package/dist/types/core.js +2 -1
  353. package/dist/types/core.js.map +1 -1
  354. package/dist/types/index.cjs +5 -0
  355. package/dist/types/index.cjs.map +1 -1
  356. package/dist/types/index.d.cts +2 -0
  357. package/dist/types/index.d.cts.map +1 -1
  358. package/dist/types/index.d.ts +2 -0
  359. package/dist/types/index.d.ts.map +1 -1
  360. package/dist/types/index.js +2 -0
  361. package/dist/types/index.js.map +1 -1
  362. package/dist/types/interfaces.d.cts +15 -8
  363. package/dist/types/interfaces.d.cts.map +1 -1
  364. package/dist/types/interfaces.d.ts +15 -8
  365. package/dist/types/interfaces.d.ts.map +1 -1
  366. package/dist/types/profiler.cjs +11 -0
  367. package/dist/types/profiler.cjs.map +1 -0
  368. package/dist/types/profiler.d.cts +100 -0
  369. package/dist/types/profiler.d.cts.map +1 -0
  370. package/dist/types/profiler.d.ts +100 -0
  371. package/dist/types/profiler.d.ts.map +1 -0
  372. package/dist/types/profiler.js +10 -0
  373. package/dist/types/profiler.js.map +1 -0
  374. package/dist/types/utility.cjs.map +1 -1
  375. package/dist/types/utility.d.cts +0 -8
  376. package/dist/types/utility.d.cts.map +1 -1
  377. package/dist/types/utility.d.ts +0 -8
  378. package/dist/types/utility.d.ts.map +1 -1
  379. package/dist/types/utility.js.map +1 -1
  380. package/dist/utils/ansi.cjs +61 -0
  381. package/dist/utils/ansi.cjs.map +1 -0
  382. package/dist/utils/ansi.d.cts +53 -0
  383. package/dist/utils/ansi.d.cts.map +1 -0
  384. package/dist/utils/ansi.d.ts +53 -0
  385. package/dist/utils/ansi.d.ts.map +1 -0
  386. package/dist/utils/ansi.js +57 -0
  387. package/dist/utils/ansi.js.map +1 -0
  388. package/dist/utils/identifiers.cjs +32 -0
  389. package/dist/utils/identifiers.cjs.map +1 -0
  390. package/dist/utils/identifiers.d.cts +32 -0
  391. package/dist/utils/identifiers.d.cts.map +1 -0
  392. package/dist/utils/identifiers.d.ts +32 -0
  393. package/dist/utils/identifiers.d.ts.map +1 -0
  394. package/dist/utils/identifiers.js +27 -0
  395. package/dist/utils/identifiers.js.map +1 -0
  396. package/dist/utils/package.cjs +40 -0
  397. package/dist/utils/package.cjs.map +1 -0
  398. package/dist/utils/package.d.cts +15 -0
  399. package/dist/utils/package.d.cts.map +1 -0
  400. package/dist/utils/package.d.ts +15 -0
  401. package/dist/utils/package.d.ts.map +1 -0
  402. package/dist/utils/package.js +33 -0
  403. package/dist/utils/package.js.map +1 -0
  404. package/dist/utils/type-guards.cjs +48 -0
  405. package/dist/utils/type-guards.cjs.map +1 -0
  406. package/dist/utils/type-guards.d.cts +22 -0
  407. package/dist/utils/type-guards.d.cts.map +1 -0
  408. package/dist/utils/type-guards.d.ts +22 -0
  409. package/dist/utils/type-guards.d.ts.map +1 -0
  410. package/dist/utils/type-guards.js +43 -0
  411. package/dist/utils/type-guards.js.map +1 -0
  412. package/package.json +14 -13
  413. package/src/bootstrap.ts +5 -5
  414. package/src/cli/commands/analyze.ts +101 -0
  415. package/src/cli/commands/baseline.ts +577 -0
  416. package/src/cli/commands/history.ts +194 -342
  417. package/src/cli/commands/init.ts +105 -183
  418. package/src/cli/commands/run.ts +186 -88
  419. package/src/cli/index.ts +731 -234
  420. package/src/config/budget-schema.ts +189 -0
  421. package/src/config/schema.ts +260 -1
  422. package/src/constants.ts +53 -1
  423. package/src/core/engine.ts +153 -14
  424. package/src/core/output-path-resolver.ts +46 -0
  425. package/src/errors/base.ts +11 -2
  426. package/src/errors/budget.ts +38 -0
  427. package/src/errors/index.ts +3 -0
  428. package/src/formatters/history/base.ts +28 -0
  429. package/src/formatters/history/compare.ts +186 -0
  430. package/src/formatters/history/list.ts +101 -0
  431. package/src/formatters/history/show.ts +155 -0
  432. package/src/formatters/history/trends.ts +281 -0
  433. package/src/formatters/history/visualization.ts +93 -0
  434. package/src/index.ts +17 -12
  435. package/src/reporters/csv.ts +55 -26
  436. package/src/reporters/human.ts +90 -89
  437. package/src/reporters/json.ts +27 -72
  438. package/src/reporters/profile-human.ts +204 -0
  439. package/src/reporters/simple.ts +85 -54
  440. package/src/services/baseline-storage.ts +199 -0
  441. package/src/services/budget-evaluator.ts +182 -0
  442. package/src/{config/manager.ts → services/config-manager.ts} +24 -9
  443. package/src/{core/loader.ts → services/file-loader.ts} +4 -7
  444. package/src/services/history/comparison.ts +130 -0
  445. package/src/services/history/models.ts +148 -0
  446. package/src/services/history/query.ts +116 -0
  447. package/src/services/history/trend-analysis.ts +238 -0
  448. package/src/services/profiler/profile-filter.ts +143 -0
  449. package/src/services/profiler/profile-parser.ts +194 -0
  450. package/src/services/profiler/profile-runner.ts +121 -0
  451. package/src/{reporters/registry.ts → services/reporter-registry.ts} +46 -81
  452. package/src/types/budgets.ts +180 -0
  453. package/src/types/cli.ts +5 -235
  454. package/src/types/core.ts +50 -10
  455. package/src/types/index.ts +5 -0
  456. package/src/types/interfaces.ts +16 -6
  457. package/src/types/profiler.ts +132 -0
  458. package/src/types/utility.ts +0 -10
  459. package/src/utils/ansi.ts +59 -0
  460. package/src/utils/identifiers.ts +58 -0
  461. package/src/utils/package.ts +35 -0
  462. package/src/utils/type-guards.ts +51 -0
  463. package/dist/config/manager.cjs.map +0 -1
  464. package/dist/config/manager.d.cts.map +0 -1
  465. package/dist/config/manager.d.ts.map +0 -1
  466. package/dist/config/manager.js.map +0 -1
  467. package/dist/core/loader.cjs.map +0 -1
  468. package/dist/core/loader.d.cts.map +0 -1
  469. package/dist/core/loader.d.ts.map +0 -1
  470. package/dist/core/loader.js.map +0 -1
  471. package/dist/progress/manager.cjs.map +0 -1
  472. package/dist/progress/manager.d.cts.map +0 -1
  473. package/dist/progress/manager.d.ts.map +0 -1
  474. package/dist/progress/manager.js.map +0 -1
  475. package/dist/reporters/registry.cjs.map +0 -1
  476. package/dist/reporters/registry.d.cts.map +0 -1
  477. package/dist/reporters/registry.d.ts.map +0 -1
  478. package/dist/reporters/registry.js.map +0 -1
  479. package/dist/storage/history.cjs.map +0 -1
  480. package/dist/storage/history.d.cts.map +0 -1
  481. package/dist/storage/history.d.ts.map +0 -1
  482. package/dist/storage/history.js.map +0 -1
  483. /package/src/{storage/history.ts → services/history-storage.ts} +0 -0
  484. /package/src/{progress/manager.ts → services/progress-manager.ts} +0 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Trend Analysis Service
3
+ *
4
+ * Statistical analysis of benchmark performance trends over time, including
5
+ * regression detection and trend classification.
6
+ */
7
+
8
+ import type { BenchmarkRun } from '../../types/index.js';
9
+ import type {
10
+ TrendData,
11
+ TrendDataPoint,
12
+ TrendsResult,
13
+ TrendStatistics,
14
+ } from './models.js';
15
+
16
+ /**
17
+ * Service for analyzing performance trends
18
+ */
19
+ export class TrendAnalysisService {
20
+ /**
21
+ * Analyze trends across multiple benchmark runs
22
+ */
23
+ analyzeTrends(runs: BenchmarkRun[]): TrendsResult {
24
+ // Build trend data for each task across runs
25
+ const taskTrendsMap = new Map<string, TrendDataPoint[]>();
26
+
27
+ for (const run of runs) {
28
+ for (const file of run.files) {
29
+ for (const suite of file.suites) {
30
+ for (const task of suite.tasks) {
31
+ if (!task.error) {
32
+ const key = `${file.filePath}::${suite.name}::${task.name}`;
33
+ const dataPoints = taskTrendsMap.get(key) || [];
34
+ dataPoints.push({
35
+ date: new Date(run.startTime),
36
+ mean: task.mean,
37
+ });
38
+ taskTrendsMap.set(key, dataPoints);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ // Calculate trends for each task
46
+ const trendsData: TrendData[] = [];
47
+
48
+ for (const [key, dataPoints] of taskTrendsMap.entries()) {
49
+ // Sort by date (oldest first)
50
+ dataPoints.sort((a, b) => a.date.getTime() - b.date.getTime());
51
+
52
+ const [_filePath, suiteName, taskName] = key.split('::');
53
+
54
+ const statistics = calculateStatistics(dataPoints);
55
+ const trend = calculateTrend(dataPoints);
56
+ const percentChange = calculatePercentChange(dataPoints);
57
+
58
+ trendsData.push({
59
+ confidence: 95, // Fixed confidence level for now
60
+ dataPoints,
61
+ percentChange,
62
+ runs: dataPoints.length,
63
+ statistics,
64
+ task: `${suiteName} › ${taskName}`,
65
+ trend,
66
+ });
67
+ }
68
+
69
+ // Sort by absolute percent change (most significant first)
70
+ trendsData.sort(
71
+ (a, b) => Math.abs(b.percentChange) - Math.abs(a.percentChange),
72
+ );
73
+
74
+ // Calculate summary statistics
75
+ const summary = {
76
+ degradingTasks: trendsData.filter((t) => t.trend === 'degrading').length,
77
+ improvingTasks: trendsData.filter((t) => t.trend === 'improving').length,
78
+ stableTasks: trendsData.filter((t) => t.trend === 'stable').length,
79
+ totalTasks: trendsData.length,
80
+ };
81
+
82
+ // Get timespan
83
+ const firstRun = runs[runs.length - 1];
84
+ const lastRun = runs[0];
85
+ const timespan = {
86
+ end: lastRun ? new Date(lastRun.startTime) : new Date(),
87
+ start: firstRun ? new Date(firstRun.startTime) : new Date(),
88
+ };
89
+
90
+ // Detect regressions (require minimum 5 runs for confidence)
91
+ const minRunsForRegression = 5;
92
+ const regressions = trendsData.filter((t) =>
93
+ detectRegression(t, 5, minRunsForRegression),
94
+ );
95
+
96
+ // Also track low-confidence potential regressions (2-4 runs showing degradation)
97
+ const lowConfidenceRegressions = trendsData.filter(
98
+ (t) =>
99
+ t.trend === 'degrading' &&
100
+ t.percentChange >= 5 &&
101
+ t.runs < minRunsForRegression &&
102
+ t.runs >= 2,
103
+ );
104
+
105
+ return {
106
+ lowConfidenceRegressions,
107
+ regressions,
108
+ runs: runs.length,
109
+ summary,
110
+ timespan,
111
+ trends: trendsData,
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Calculate percent change from first to last data point
118
+ */
119
+ export const calculatePercentChange = (
120
+ dataPoints: TrendDataPoint[],
121
+ ): number => {
122
+ if (dataPoints.length === 0 || dataPoints.length === 1) {
123
+ return 0;
124
+ }
125
+
126
+ const firstPoint = dataPoints[0];
127
+ const lastPoint = dataPoints[dataPoints.length - 1];
128
+
129
+ if (!firstPoint || !lastPoint) {
130
+ return 0;
131
+ }
132
+
133
+ const first = firstPoint.mean;
134
+ const last = lastPoint.mean;
135
+
136
+ if (first === 0) {
137
+ return 0; // Avoid division by zero
138
+ }
139
+
140
+ return ((last - first) / first) * 100;
141
+ };
142
+
143
+ /**
144
+ * Calculate statistical metrics from data points
145
+ */
146
+ export const calculateStatistics = (
147
+ dataPoints: TrendDataPoint[],
148
+ ): TrendStatistics => {
149
+ if (dataPoints.length === 0) {
150
+ throw new Error('Cannot calculate statistics for empty data points array');
151
+ }
152
+
153
+ const values = dataPoints.map((dp) => dp.mean);
154
+ const n = values.length;
155
+
156
+ // Calculate mean
157
+ const mean = values.reduce((sum, val) => sum + val, 0) / n;
158
+
159
+ // Calculate median
160
+ const sorted = [...values].sort((a, b) => a - b);
161
+ const median =
162
+ n % 2 === 0
163
+ ? ((sorted[n / 2 - 1] ?? 0) + (sorted[n / 2] ?? 0)) / 2
164
+ : (sorted[Math.floor(n / 2)] ?? 0);
165
+
166
+ // Calculate variance and standard deviation
167
+ const variance =
168
+ n === 1
169
+ ? 0
170
+ : values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / n;
171
+ const stdDeviation = Math.sqrt(variance);
172
+
173
+ return {
174
+ mean,
175
+ median,
176
+ stdDeviation,
177
+ variance,
178
+ };
179
+ };
180
+
181
+ /**
182
+ * Calculate trend direction from data points using linear regression
183
+ */
184
+ export const calculateTrend = (
185
+ dataPoints: TrendDataPoint[],
186
+ ): 'degrading' | 'improving' | 'stable' => {
187
+ if (dataPoints.length === 0) {
188
+ return 'stable';
189
+ }
190
+
191
+ if (dataPoints.length === 1) {
192
+ return 'stable';
193
+ }
194
+
195
+ // Simple linear regression to determine slope
196
+ const n = dataPoints.length;
197
+ const x = Array.from({ length: n }, (_, i) => i); // Time indices
198
+ const y = dataPoints.map((dp) => dp.mean);
199
+
200
+ const sumX = x.reduce((a, b) => a + b, 0);
201
+ const sumY = y.reduce((a, b) => a + b, 0);
202
+ const sumXY = x.reduce((sum, xi, i) => sum + xi * (y[i] ?? 0), 0);
203
+ const sumXX = x.reduce((sum, xi) => sum + xi * xi, 0);
204
+
205
+ // Calculate slope: (n*sumXY - sumX*sumY) / (n*sumXX - sumX*sumX)
206
+ const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
207
+
208
+ // Determine significance threshold (5% of mean)
209
+ const meanValue = sumY / n;
210
+ const significanceThreshold = Math.abs(meanValue * 0.05);
211
+
212
+ // Classify trend based on slope
213
+ if (Math.abs(slope) < significanceThreshold / n) {
214
+ return 'stable';
215
+ } else if (slope < 0) {
216
+ // Negative slope = values decreasing = performance improving
217
+ return 'improving';
218
+ } else {
219
+ // Positive slope = values increasing = performance degrading
220
+ return 'degrading';
221
+ }
222
+ };
223
+
224
+ /**
225
+ * Detect if a trend represents a performance regression
226
+ */
227
+ export const detectRegression = (
228
+ trendData: TrendData,
229
+ threshold: number,
230
+ minRuns: number,
231
+ ): boolean => {
232
+ // Regression is a degrading trend with percent change exceeding threshold
233
+ return (
234
+ trendData.trend === 'degrading' &&
235
+ trendData.percentChange >= threshold &&
236
+ trendData.runs >= minRuns
237
+ );
238
+ };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Profile Filter Service
3
+ *
4
+ * Filters and sorts profiled functions based on configuration. Implements smart
5
+ * detection to focus on user code by excluding node_modules and Node.js
6
+ * internals.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import { minimatch } from 'minimatch';
12
+
13
+ import type {
14
+ FilteredProfileData,
15
+ ProfileConfig,
16
+ ProfiledFunction,
17
+ RawProfileData,
18
+ } from '../../types/profiler.js';
19
+
20
+ /**
21
+ * Filter profile data based on configuration
22
+ *
23
+ * @param data - Raw profile data
24
+ * @param config - Filter configuration
25
+ * @param packageRoot - Package root directory for smart detection
26
+ * @returns Filtered profile data
27
+ */
28
+ export const filterProfile = (
29
+ data: RawProfileData,
30
+ config: ProfileConfig,
31
+ packageRoot: string,
32
+ ): FilteredProfileData => {
33
+ let filtered = data.functions.filter((fn) => {
34
+ // Only JavaScript functions
35
+ if (fn.category !== 'JavaScript') {
36
+ return false;
37
+ }
38
+
39
+ // Apply smart detection if enabled
40
+ if (config.smartDetection && !config.focus?.length) {
41
+ if (!isUserCode(fn.file, packageRoot)) {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ // Apply focus patterns (if provided, overrides smart detection)
47
+ if (config.focus?.length) {
48
+ if (!matchesAnyPattern(fn.file, config.focus)) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ // Apply exclude patterns (always applied if provided)
54
+ if (config.exclude?.length) {
55
+ if (matchesAnyPattern(fn.file, config.exclude)) {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ return true;
61
+ });
62
+
63
+ // Apply percentage threshold
64
+ const minPercent = config.minExecutionPercent ?? 0.5;
65
+ filtered = filtered.filter((fn) => fn.percentage >= minPercent);
66
+
67
+ // Sort by percentage (highest first)
68
+ filtered.sort((a, b) => b.percentage - a.percentage);
69
+
70
+ // Limit to topN
71
+ const topN = config.topN ?? 25;
72
+ filtered = filtered.slice(0, topN);
73
+
74
+ // Group by file if requested
75
+ let groupedByFile: Map<string, ProfiledFunction[]> | undefined;
76
+ if (config.groupByFile) {
77
+ groupedByFile = groupByFile(filtered);
78
+ }
79
+
80
+ return {
81
+ functions: filtered,
82
+ groupedByFile,
83
+ summary: data.summary,
84
+ totalFiltered: data.functions.length,
85
+ totalShown: filtered.length,
86
+ totalTicks: data.totalTicks,
87
+ };
88
+ };
89
+
90
+ /**
91
+ * Check if a file path is user code (not node_modules or internals)
92
+ */
93
+ const isUserCode = (filePath: string, packageRoot: string): boolean => {
94
+ // Exclude node_modules
95
+ if (
96
+ filePath.includes('/node_modules/') ||
97
+ filePath.includes('\\node_modules\\')
98
+ ) {
99
+ return false;
100
+ }
101
+
102
+ // Exclude Node.js internals
103
+ if (filePath.startsWith('node:') || filePath.startsWith('internal/')) {
104
+ return false;
105
+ }
106
+
107
+ // Allow <unknown> files (could be eval'd code or other user code without file paths)
108
+ if (filePath === '<unknown>' || filePath === '[eval]') {
109
+ return true;
110
+ }
111
+
112
+ // Must be within package root
113
+ return filePath.startsWith(packageRoot);
114
+ };
115
+
116
+ /**
117
+ * Check if a file path matches any of the given glob patterns
118
+ */
119
+ const matchesAnyPattern = (filePath: string, patterns: string[]): boolean => {
120
+ return patterns.some((pattern) => minimatch(filePath, pattern));
121
+ };
122
+
123
+ /**
124
+ * Group functions by file path
125
+ */
126
+ const groupByFile = (
127
+ functions: ProfiledFunction[],
128
+ ): Map<string, ProfiledFunction[]> => {
129
+ const grouped = new Map<string, ProfiledFunction[]>();
130
+
131
+ for (const fn of functions) {
132
+ const existing = grouped.get(fn.file) || [];
133
+ existing.push(fn);
134
+ grouped.set(fn.file, existing);
135
+ }
136
+
137
+ // Sort functions within each file by percentage
138
+ for (const [, fns] of grouped.entries()) {
139
+ fns.sort((a, b) => b.percentage - a.percentage);
140
+ }
141
+
142
+ return grouped;
143
+ };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Profile Parser Service
3
+ *
4
+ * Parses Chrome DevTools CPU profile format (*.cpuprofile files) generated by
5
+ * Node.js --cpu-prof flag. Extracts JavaScript function execution data
6
+ * including file paths, line numbers, hit counts, and percentages.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import { readFile } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ import type {
16
+ ProfiledFunction,
17
+ ProfileSummary,
18
+ RawProfileData,
19
+ } from '../../types/profiler.js';
20
+
21
+ interface CallFrame {
22
+ columnNumber: number;
23
+ functionName: string;
24
+ lineNumber: number;
25
+ scriptId: string;
26
+ url: string;
27
+ }
28
+
29
+ /**
30
+ * Chrome DevTools CPU profile format
31
+ */
32
+ interface CpuProfile {
33
+ endTime: number;
34
+ nodes: ProfileNode[];
35
+ samples?: number[];
36
+ startTime: number;
37
+ }
38
+
39
+ interface ProfileNode {
40
+ callFrame: CallFrame;
41
+ children?: number[];
42
+ hitCount?: number;
43
+ id: number;
44
+ }
45
+
46
+ /**
47
+ * Parse a CPU profile file
48
+ *
49
+ * @param profilePath - Path to *.cpuprofile file
50
+ * @returns Parsed profile data
51
+ */
52
+ export const parseProfile = async (
53
+ profilePath: string,
54
+ ): Promise<RawProfileData> => {
55
+ // Read and parse JSON profile
56
+ const content = await readFile(profilePath, 'utf-8');
57
+ const profile: CpuProfile = JSON.parse(content) as CpuProfile;
58
+
59
+ // Extract functions and calculate statistics
60
+ return parseCpuProfile(profile, profilePath);
61
+ };
62
+
63
+ /**
64
+ * Parse CPU profile data structure
65
+ */
66
+ const parseCpuProfile = (
67
+ profile: CpuProfile,
68
+ profilePath: string,
69
+ ): RawProfileData => {
70
+ const functions: ProfiledFunction[] = [];
71
+ let totalTicks = 0;
72
+
73
+ // Calculate total ticks from all node hit counts
74
+ for (const node of profile.nodes) {
75
+ totalTicks += node.hitCount || 0;
76
+ }
77
+
78
+ // Extract function information from nodes with hit counts
79
+ for (const node of profile.nodes) {
80
+ const hitCount = node.hitCount || 0;
81
+ if (hitCount === 0) {
82
+ continue; // Skip nodes with no samples
83
+ }
84
+
85
+ const { callFrame } = node;
86
+ const percentage = totalTicks > 0 ? (hitCount / totalTicks) * 100 : 0;
87
+
88
+ // Parse file path from URL
89
+ const filePath = parseFileUrl(callFrame.url);
90
+
91
+ // Determine category based on URL
92
+ const category = determineCategory(callFrame.url);
93
+
94
+ functions.push({
95
+ category,
96
+ file: filePath,
97
+ line: callFrame.lineNumber >= 0 ? callFrame.lineNumber + 1 : null, // DevTools uses 0-based line numbers
98
+ name: callFrame.functionName || '(anonymous)',
99
+ percentage,
100
+ ticks: hitCount,
101
+ });
102
+ }
103
+
104
+ // Calculate summary statistics
105
+ const javascriptTicks = functions
106
+ .filter((fn) => fn.category === 'JavaScript')
107
+ .reduce((sum, fn) => sum + fn.ticks, 0);
108
+
109
+ const cppTicks = functions
110
+ .filter((fn) => fn.category === 'C++')
111
+ .reduce((sum, fn) => sum + fn.ticks, 0);
112
+
113
+ const summary: ProfileSummary = {
114
+ cppTicks,
115
+ gcTicks: 0, // CPU profiles don't separate GC
116
+ javascriptTicks,
117
+ sharedLibraryTicks: 0,
118
+ totalTicks,
119
+ };
120
+
121
+ return {
122
+ functions,
123
+ logPath: profilePath,
124
+ summary,
125
+ totalTicks,
126
+ };
127
+ };
128
+
129
+ /**
130
+ * Parse file URL from call frame
131
+ */
132
+ const parseFileUrl = (url: string): string => {
133
+ if (!url) {
134
+ return '<unknown>';
135
+ }
136
+
137
+ // Handle file:// URLs
138
+ if (url.startsWith('file://')) {
139
+ try {
140
+ return fileURLToPath(url);
141
+ } catch {
142
+ // Fallback if URL is malformed
143
+ return path.normalize(url.replace('file://', ''));
144
+ }
145
+ }
146
+
147
+ // Handle node: internal modules
148
+ if (url.startsWith('node:')) {
149
+ return url;
150
+ }
151
+
152
+ // Handle plain paths
153
+ return path.normalize(url);
154
+ };
155
+
156
+ /**
157
+ * Determine function category based on URL
158
+ */
159
+ const determineCategory = (
160
+ url: string,
161
+ ): 'C++' | 'GC' | 'JavaScript' | 'Unknown' => {
162
+ if (!url) {
163
+ return 'Unknown';
164
+ }
165
+
166
+ // Node.js internals and native modules
167
+ if (url.startsWith('node:') || url.includes('[native code]')) {
168
+ return 'C++';
169
+ }
170
+
171
+ // eval'd code is JavaScript
172
+ if (url === '[eval]') {
173
+ return 'JavaScript';
174
+ }
175
+
176
+ // JavaScript files
177
+ if (
178
+ url.endsWith('.js') ||
179
+ url.endsWith('.mjs') ||
180
+ url.endsWith('.cjs') ||
181
+ url.endsWith('.ts') ||
182
+ url.endsWith('.mts') ||
183
+ url.endsWith('.cts')
184
+ ) {
185
+ return 'JavaScript';
186
+ }
187
+
188
+ // Default to JavaScript for file:// URLs
189
+ if (url.startsWith('file://')) {
190
+ return 'JavaScript';
191
+ }
192
+
193
+ return 'Unknown';
194
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Profile Runner Service
3
+ *
4
+ * Executes commands with Node.js CPU profiling enabled and captures profiler
5
+ * output to *.cpuprofile files in .modestbench/profiles/.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import { glob } from 'glob';
11
+ import { spawn } from 'node:child_process';
12
+ import { mkdir, stat } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+
15
+ /**
16
+ * Options for running with profiling
17
+ */
18
+ interface RunOptions {
19
+ /** Working directory */
20
+ cwd?: string;
21
+
22
+ /** Environment variables */
23
+ env?: NodeJS.ProcessEnv;
24
+
25
+ /** Timeout in milliseconds */
26
+ timeout?: number;
27
+ }
28
+
29
+ /**
30
+ * Run a command with Node.js profiling enabled
31
+ *
32
+ * @param command - Command to run (e.g., "npm test")
33
+ * @param options - Execution options
34
+ * @returns Path to generated *.cpuprofile file
35
+ */
36
+ export const runWithProfiling = async (
37
+ command: string,
38
+ options: RunOptions = {},
39
+ ): Promise<string> => {
40
+ const cwd = options.cwd || process.cwd();
41
+
42
+ // Create profiles directory
43
+ const profilesDir = join(cwd, '.modestbench', 'profiles');
44
+ await mkdir(profilesDir, { recursive: true });
45
+
46
+ // Run command with NODE_OPTIONS="--cpu-prof --cpu-prof-dir=..."
47
+ const proc = spawn(command, {
48
+ cwd,
49
+ env: {
50
+ ...process.env,
51
+ ...options.env,
52
+ NODE_OPTIONS: `--cpu-prof --cpu-prof-dir=${profilesDir}`,
53
+ },
54
+ shell: true,
55
+ stdio: 'inherit',
56
+ });
57
+
58
+ // Wait for process to complete
59
+ await new Promise<void>((resolve, reject) => {
60
+ const timeout = options.timeout
61
+ ? setTimeout(() => {
62
+ proc.kill();
63
+ reject(
64
+ new Error(`Profile command timed out after ${options.timeout}ms`),
65
+ );
66
+ }, options.timeout)
67
+ : null;
68
+
69
+ proc.on('close', (code) => {
70
+ if (timeout) {
71
+ clearTimeout(timeout);
72
+ }
73
+ if (code === 0 || code === null) {
74
+ resolve();
75
+ } else {
76
+ reject(new Error(`Profile command exited with code ${code}`));
77
+ }
78
+ });
79
+
80
+ proc.on('error', (err) => {
81
+ if (timeout) {
82
+ clearTimeout(timeout);
83
+ }
84
+ reject(err);
85
+ });
86
+ });
87
+
88
+ // Find generated *.cpuprofile file in profiles directory
89
+ const profileFiles = await glob('*.cpuprofile', { cwd: profilesDir });
90
+
91
+ if (profileFiles.length === 0) {
92
+ throw new Error(
93
+ 'No CPU profile generated. Ensure the command runs Node.js code.',
94
+ );
95
+ }
96
+
97
+ // Return most recent profile file
98
+ return await getMostRecentFile(profileFiles, profilesDir);
99
+ };
100
+
101
+ /**
102
+ * Get the most recently modified file from a list
103
+ */
104
+ const getMostRecentFile = async (
105
+ files: string[],
106
+ cwd: string,
107
+ ): Promise<string> => {
108
+ let mostRecent = files[0];
109
+ let mostRecentTime = (await stat(`${cwd}/${mostRecent}`)).mtimeMs;
110
+
111
+ for (const file of files.slice(1)) {
112
+ const filePath = `${cwd}/${file}`;
113
+ const fileTime = (await stat(filePath)).mtimeMs;
114
+ if (fileTime > mostRecentTime) {
115
+ mostRecent = file;
116
+ mostRecentTime = fileTime;
117
+ }
118
+ }
119
+
120
+ return `${cwd}/${mostRecent}`;
121
+ };