modestbench 0.3.2 → 0.5.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 (184) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +45 -2
  3. package/dist/adapters/ava-adapter.cjs +421 -0
  4. package/dist/adapters/ava-adapter.cjs.map +1 -0
  5. package/dist/adapters/ava-adapter.d.cts +39 -0
  6. package/dist/adapters/ava-adapter.d.cts.map +1 -0
  7. package/dist/adapters/ava-adapter.d.ts +39 -0
  8. package/dist/adapters/ava-adapter.d.ts.map +1 -0
  9. package/dist/adapters/ava-adapter.js +384 -0
  10. package/dist/adapters/ava-adapter.js.map +1 -0
  11. package/dist/adapters/ava-hooks.cjs +66 -0
  12. package/dist/adapters/ava-hooks.cjs.map +1 -0
  13. package/dist/adapters/ava-hooks.d.cts +24 -0
  14. package/dist/adapters/ava-hooks.d.cts.map +1 -0
  15. package/dist/adapters/ava-hooks.d.ts +24 -0
  16. package/dist/adapters/ava-hooks.d.ts.map +1 -0
  17. package/dist/adapters/ava-hooks.js +61 -0
  18. package/dist/adapters/ava-hooks.js.map +1 -0
  19. package/dist/adapters/ava-register.cjs +16 -0
  20. package/dist/adapters/ava-register.cjs.map +1 -0
  21. package/dist/adapters/ava-register.d.cts +11 -0
  22. package/dist/adapters/ava-register.d.cts.map +1 -0
  23. package/dist/adapters/ava-register.d.ts +11 -0
  24. package/dist/adapters/ava-register.d.ts.map +1 -0
  25. package/dist/adapters/ava-register.js +14 -0
  26. package/dist/adapters/ava-register.js.map +1 -0
  27. package/dist/adapters/mocha-adapter.cjs +254 -0
  28. package/dist/adapters/mocha-adapter.cjs.map +1 -0
  29. package/dist/adapters/mocha-adapter.d.cts +26 -0
  30. package/dist/adapters/mocha-adapter.d.cts.map +1 -0
  31. package/dist/adapters/mocha-adapter.d.ts +26 -0
  32. package/dist/adapters/mocha-adapter.d.ts.map +1 -0
  33. package/dist/adapters/mocha-adapter.js +217 -0
  34. package/dist/adapters/mocha-adapter.js.map +1 -0
  35. package/dist/adapters/node-test-adapter.cjs +335 -0
  36. package/dist/adapters/node-test-adapter.cjs.map +1 -0
  37. package/dist/adapters/node-test-adapter.d.cts +41 -0
  38. package/dist/adapters/node-test-adapter.d.cts.map +1 -0
  39. package/dist/adapters/node-test-adapter.d.ts +41 -0
  40. package/dist/adapters/node-test-adapter.d.ts.map +1 -0
  41. package/dist/adapters/node-test-adapter.js +298 -0
  42. package/dist/adapters/node-test-adapter.js.map +1 -0
  43. package/dist/adapters/node-test-hooks.cjs +72 -0
  44. package/dist/adapters/node-test-hooks.cjs.map +1 -0
  45. package/dist/adapters/node-test-hooks.d.cts +24 -0
  46. package/dist/adapters/node-test-hooks.d.cts.map +1 -0
  47. package/dist/adapters/node-test-hooks.d.ts +24 -0
  48. package/dist/adapters/node-test-hooks.d.ts.map +1 -0
  49. package/dist/adapters/node-test-hooks.js +67 -0
  50. package/dist/adapters/node-test-hooks.js.map +1 -0
  51. package/dist/adapters/node-test-register.cjs +7 -0
  52. package/dist/adapters/node-test-register.cjs.map +1 -0
  53. package/dist/adapters/node-test-register.d.cts +2 -0
  54. package/dist/adapters/node-test-register.d.cts.map +1 -0
  55. package/dist/adapters/node-test-register.d.ts +2 -0
  56. package/dist/adapters/node-test-register.d.ts.map +1 -0
  57. package/dist/adapters/node-test-register.js +5 -0
  58. package/dist/adapters/node-test-register.js.map +1 -0
  59. package/dist/adapters/types.cjs +152 -0
  60. package/dist/adapters/types.cjs.map +1 -0
  61. package/dist/adapters/types.d.cts +112 -0
  62. package/dist/adapters/types.d.cts.map +1 -0
  63. package/dist/adapters/types.d.ts +112 -0
  64. package/dist/adapters/types.d.ts.map +1 -0
  65. package/dist/adapters/types.js +148 -0
  66. package/dist/adapters/types.js.map +1 -0
  67. package/dist/cli/commands/init.cjs +21 -17
  68. package/dist/cli/commands/init.cjs.map +1 -1
  69. package/dist/cli/commands/init.d.cts.map +1 -1
  70. package/dist/cli/commands/init.d.ts.map +1 -1
  71. package/dist/cli/commands/init.js +21 -17
  72. package/dist/cli/commands/init.js.map +1 -1
  73. package/dist/cli/commands/run.cjs +6 -2
  74. package/dist/cli/commands/run.cjs.map +1 -1
  75. package/dist/cli/commands/run.js +6 -2
  76. package/dist/cli/commands/run.js.map +1 -1
  77. package/dist/cli/commands/test.cjs +392 -0
  78. package/dist/cli/commands/test.cjs.map +1 -0
  79. package/dist/cli/commands/test.d.cts +38 -0
  80. package/dist/cli/commands/test.d.cts.map +1 -0
  81. package/dist/cli/commands/test.d.ts +38 -0
  82. package/dist/cli/commands/test.d.ts.map +1 -0
  83. package/dist/cli/commands/test.js +388 -0
  84. package/dist/cli/commands/test.js.map +1 -0
  85. package/dist/cli/index.cjs +72 -1
  86. package/dist/cli/index.cjs.map +1 -1
  87. package/dist/cli/index.d.cts.map +1 -1
  88. package/dist/cli/index.d.ts.map +1 -1
  89. package/dist/cli/index.js +73 -2
  90. package/dist/cli/index.js.map +1 -1
  91. package/dist/constants.cjs +13 -1
  92. package/dist/constants.cjs.map +1 -1
  93. package/dist/constants.d.cts +12 -0
  94. package/dist/constants.d.cts.map +1 -1
  95. package/dist/constants.d.ts +12 -0
  96. package/dist/constants.d.ts.map +1 -1
  97. package/dist/constants.js +12 -0
  98. package/dist/constants.js.map +1 -1
  99. package/dist/core/engine.cjs +4 -0
  100. package/dist/core/engine.cjs.map +1 -1
  101. package/dist/core/engine.d.cts.map +1 -1
  102. package/dist/core/engine.d.ts.map +1 -1
  103. package/dist/core/engine.js +4 -0
  104. package/dist/core/engine.js.map +1 -1
  105. package/dist/core/engines/tinybench-engine.cjs +163 -131
  106. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  107. package/dist/core/engines/tinybench-engine.d.cts +6 -0
  108. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  109. package/dist/core/engines/tinybench-engine.d.ts +6 -0
  110. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  111. package/dist/core/engines/tinybench-engine.js +163 -131
  112. package/dist/core/engines/tinybench-engine.js.map +1 -1
  113. package/dist/core/stats-utils.cjs +4 -2
  114. package/dist/core/stats-utils.cjs.map +1 -1
  115. package/dist/core/stats-utils.d.cts +1 -1
  116. package/dist/core/stats-utils.d.cts.map +1 -1
  117. package/dist/core/stats-utils.d.ts +1 -1
  118. package/dist/core/stats-utils.d.ts.map +1 -1
  119. package/dist/core/stats-utils.js +4 -2
  120. package/dist/core/stats-utils.js.map +1 -1
  121. package/dist/errors/base.cjs +2 -1
  122. package/dist/errors/base.cjs.map +1 -1
  123. package/dist/errors/base.d.cts.map +1 -1
  124. package/dist/errors/base.d.ts.map +1 -1
  125. package/dist/errors/base.js +2 -1
  126. package/dist/errors/base.js.map +1 -1
  127. package/dist/formatters/history/compare.cjs.map +1 -1
  128. package/dist/formatters/history/compare.d.cts.map +1 -1
  129. package/dist/formatters/history/compare.d.ts.map +1 -1
  130. package/dist/formatters/history/compare.js.map +1 -1
  131. package/dist/formatters/history/list.cjs.map +1 -1
  132. package/dist/formatters/history/list.d.cts.map +1 -1
  133. package/dist/formatters/history/list.d.ts.map +1 -1
  134. package/dist/formatters/history/list.js.map +1 -1
  135. package/dist/reporters/human.cjs +83 -27
  136. package/dist/reporters/human.cjs.map +1 -1
  137. package/dist/reporters/human.d.cts +1 -0
  138. package/dist/reporters/human.d.cts.map +1 -1
  139. package/dist/reporters/human.d.ts +1 -0
  140. package/dist/reporters/human.d.ts.map +1 -1
  141. package/dist/reporters/human.js +83 -27
  142. package/dist/reporters/human.js.map +1 -1
  143. package/dist/reporters/simple.cjs +68 -21
  144. package/dist/reporters/simple.cjs.map +1 -1
  145. package/dist/reporters/simple.d.cts +1 -0
  146. package/dist/reporters/simple.d.cts.map +1 -1
  147. package/dist/reporters/simple.d.ts +1 -0
  148. package/dist/reporters/simple.d.ts.map +1 -1
  149. package/dist/reporters/simple.js +68 -21
  150. package/dist/reporters/simple.js.map +1 -1
  151. package/dist/services/config-manager.cjs +1 -1
  152. package/dist/services/config-manager.cjs.map +1 -1
  153. package/dist/services/config-manager.d.cts.map +1 -1
  154. package/dist/services/config-manager.d.ts.map +1 -1
  155. package/dist/services/config-manager.js +2 -2
  156. package/dist/services/config-manager.js.map +1 -1
  157. package/dist/services/file-loader.cjs.map +1 -1
  158. package/dist/services/file-loader.d.cts.map +1 -1
  159. package/dist/services/file-loader.d.ts.map +1 -1
  160. package/dist/services/file-loader.js.map +1 -1
  161. package/package.json +63 -35
  162. package/src/adapters/ava-adapter.ts +553 -0
  163. package/src/adapters/ava-hooks.ts +65 -0
  164. package/src/adapters/ava-register.ts +15 -0
  165. package/src/adapters/mocha-adapter.ts +284 -0
  166. package/src/adapters/node-test-adapter.ts +391 -0
  167. package/src/adapters/node-test-hooks.ts +71 -0
  168. package/src/adapters/node-test-register.ts +5 -0
  169. package/src/adapters/types.ts +281 -0
  170. package/src/cli/commands/init.ts +25 -17
  171. package/src/cli/commands/run.ts +9 -2
  172. package/src/cli/commands/test.ts +546 -0
  173. package/src/cli/index.ts +81 -1
  174. package/src/constants.ts +15 -0
  175. package/src/core/engine.ts +5 -0
  176. package/src/core/engines/tinybench-engine.ts +213 -141
  177. package/src/core/stats-utils.ts +5 -3
  178. package/src/errors/base.ts +3 -2
  179. package/src/formatters/history/compare.ts +1 -3
  180. package/src/formatters/history/list.ts +1 -3
  181. package/src/reporters/human.ts +107 -36
  182. package/src/reporters/simple.ts +81 -22
  183. package/src/services/config-manager.ts +4 -5
  184. package/src/services/file-loader.ts +2 -3
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ModestBench Node.js Test Runner Loader
3
+ *
4
+ * ES module loader hook that intercepts `node:test` imports and returns our
5
+ * capturing mock from globalThis.
6
+ *
7
+ * Usage: node --import modestbench/node-test your-test.js
8
+ *
9
+ * This loader exports async `resolve` and `load` hooks that get registered via
10
+ * module.register() when imported.
11
+ */
12
+
13
+ import type { LoadHook, ResolveHook } from 'node:module';
14
+
15
+ // Generate the mock module source code
16
+ // The generated module source uses top-level await to conditionally get the mock or real module
17
+ // Note: Uses 'node:test?passthrough' to bypass our hook when falling back
18
+ // Security: The globalThis mock is only installed by our own adapter code, so the generated
19
+ // source is safe. No user input is interpolated into this template.
20
+ const generateMockSource = (): string => `
21
+ const mock = globalThis.__MODESTBENCH_NODE_TEST_MOCK__;
22
+
23
+ // If no mock installed, fall through to real node:test
24
+ // The '?passthrough' query tells our hook to not intercept this import
25
+ const source = mock ?? await import('node:test?passthrough');
26
+
27
+ export const test = source.test;
28
+ export const describe = source.describe;
29
+ export const it = source.it;
30
+ export const before = source.before;
31
+ export const after = source.after;
32
+ export const beforeEach = source.beforeEach;
33
+ export const afterEach = source.afterEach;
34
+ export const suite = source.suite;
35
+ export default source.default ?? source.test;
36
+ `;
37
+
38
+ /**
39
+ * Resolve hook - intercepts node:test specifier
40
+ *
41
+ * Uses query param '?passthrough' to prevent infinite recursion when falling
42
+ * back to real node:test (when no mock is installed).
43
+ */
44
+ export const resolve: ResolveHook = async (specifier, context, nextResolve) => {
45
+ // Only intercept bare 'node:test', not 'node:test?passthrough'
46
+ if (specifier === 'node:test') {
47
+ return {
48
+ shortCircuit: true,
49
+ url: 'modestbench://capture/node-test',
50
+ };
51
+ }
52
+ // Strip passthrough query to resolve real node:test
53
+ if (specifier === 'node:test?passthrough') {
54
+ return nextResolve('node:test', context);
55
+ }
56
+ return nextResolve(specifier, context);
57
+ };
58
+
59
+ /**
60
+ * Load hook - returns mock module for our custom URL
61
+ */
62
+ export const load: LoadHook = async (url, context, nextLoad) => {
63
+ if (url === 'modestbench://capture/node-test') {
64
+ return {
65
+ format: 'module',
66
+ shortCircuit: true,
67
+ source: generateMockSource(),
68
+ };
69
+ }
70
+ return nextLoad(url, context);
71
+ };
@@ -0,0 +1,5 @@
1
+ import { register } from 'node:module';
2
+
3
+ register('./node-test-hooks.js', {
4
+ parentURL: import.meta.url,
5
+ });
@@ -0,0 +1,281 @@
1
+ /**
2
+ * ModestBench Test Framework Adapter Types
3
+ *
4
+ * Shared types for capturing test definitions from various test frameworks
5
+ * (Mocha, node:test, AVA) and converting them to modestbench benchmark format.
6
+ */
7
+
8
+ import type {
9
+ BenchmarkDefinition,
10
+ BenchmarkSuite,
11
+ } from '../core/benchmark-schema.js';
12
+
13
+ /**
14
+ * A captured test suite (describe block) from a test framework
15
+ */
16
+ export interface CapturedSuite {
17
+ /** Nested child suites */
18
+ readonly children: CapturedSuite[];
19
+ /** Lifecycle hooks for the suite */
20
+ readonly hooks: SuiteHooks;
21
+ /** Suite name */
22
+ readonly name: string;
23
+ /** Tests in this suite */
24
+ readonly tests: CapturedTest[];
25
+ }
26
+
27
+ /**
28
+ * A captured test function from a test framework
29
+ */
30
+ export interface CapturedTest {
31
+ /** Test function body */
32
+ readonly fn: () => Promise<void> | void;
33
+ /** Test name */
34
+ readonly name: string;
35
+ /** Whether this test is marked .only */
36
+ readonly only?: boolean;
37
+ /** Whether this test is marked .skip */
38
+ readonly skip?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Result of capturing tests from a single file
43
+ */
44
+ export interface CapturedTestFile {
45
+ /** File path */
46
+ readonly filePath: string;
47
+ /** Test framework detected/used */
48
+ readonly framework: TestFramework;
49
+ /** Root-level suites (describe blocks) */
50
+ readonly rootSuites: CapturedSuite[];
51
+ /** Root-level tests (not in any describe block - e.g., AVA) */
52
+ readonly rootTests: CapturedTest[];
53
+ }
54
+
55
+ /**
56
+ * Converted benchmark suite structure
57
+ *
58
+ * This is what capturedToBenchmark produces for each suite.
59
+ */
60
+ export type ConvertedBenchmarkSuite = Pick<
61
+ BenchmarkSuite,
62
+ 'benchmarks' | 'setup' | 'teardown'
63
+ >;
64
+
65
+ /**
66
+ * Lifecycle hooks captured from a test suite
67
+ */
68
+ export interface SuiteHooks {
69
+ /** Hooks to run once after all tests in suite */
70
+ readonly after: Array<() => Promise<void> | void>;
71
+ /** Hooks to run after each test */
72
+ readonly afterEach: Array<() => Promise<void> | void>;
73
+ /** Hooks to run once before all tests in suite */
74
+ readonly before: Array<() => Promise<void> | void>;
75
+ /** Hooks to run before each test */
76
+ readonly beforeEach: Array<() => Promise<void> | void>;
77
+ }
78
+
79
+ /**
80
+ * Supported test frameworks
81
+ */
82
+ export type TestFramework = 'ava' | 'mocha' | 'node-test';
83
+
84
+ /**
85
+ * Interface for test framework adapters
86
+ *
87
+ * Each adapter is responsible for:
88
+ *
89
+ * 1. Capturing test definitions from a test file
90
+ * 2. Normalizing them to the CapturedTestFile format
91
+ */
92
+ export interface TestFrameworkAdapter {
93
+ /**
94
+ * Capture test definitions from a file
95
+ *
96
+ * This method loads the test file with appropriate hooks/mocks in place to
97
+ * capture test function definitions without executing the tests.
98
+ *
99
+ * @param filePath - Absolute path to the test file
100
+ * @returns Captured test structure
101
+ */
102
+ capture(filePath: string): Promise<CapturedTestFile>;
103
+
104
+ /** Framework this adapter handles */
105
+ readonly framework: TestFramework;
106
+ }
107
+
108
+ /**
109
+ * Convert captured test file to modestbench benchmark definition
110
+ *
111
+ * Transforms the CapturedTestFile structure into the BenchmarkDefinition format
112
+ * that modestbench's engine can execute.
113
+ *
114
+ * @example
115
+ *
116
+ * ```typescript
117
+ * const captured = await adapter.capture('/path/to/test.js');
118
+ * const benchmark = capturedToBenchmark(captured);
119
+ * // benchmark.suites contains test suites converted to benchmark suites
120
+ * ```
121
+ *
122
+ * @param captured - The captured test file structure from a test framework
123
+ * adapter
124
+ * @returns A BenchmarkDefinition with suites containing benchmarks derived from
125
+ * tests
126
+ */
127
+ export const capturedToBenchmark = (
128
+ captured: CapturedTestFile,
129
+ ): BenchmarkDefinition => {
130
+ const suites: Record<string, ReturnType<typeof suiteToRecord>> = {};
131
+
132
+ // Convert root-level tests to a default suite (AVA-style)
133
+ if (captured.rootTests.length > 0) {
134
+ suites['default'] = {
135
+ benchmarks: Object.fromEntries(
136
+ captured.rootTests
137
+ .filter((t) => !t.skip)
138
+ .map((test) => [test.name, { fn: test.fn }]),
139
+ ),
140
+ };
141
+ }
142
+
143
+ // Convert captured suites to benchmark suites
144
+ for (const suite of captured.rootSuites) {
145
+ const converted = convertSuite(suite);
146
+ if (converted) {
147
+ suites[suite.name] = converted;
148
+ }
149
+ }
150
+
151
+ return { suites };
152
+ };
153
+
154
+ /**
155
+ * Convert a captured suite to benchmark suite format
156
+ */
157
+ const convertSuite = (
158
+ suite: CapturedSuite,
159
+ ): null | ReturnType<typeof suiteToRecord> => {
160
+ // Skip empty suites
161
+ const nonSkippedTests = suite.tests.filter((t) => !t.skip);
162
+ if (nonSkippedTests.length === 0 && suite.children.length === 0) {
163
+ return null;
164
+ }
165
+
166
+ return suiteToRecord(suite);
167
+ };
168
+
169
+ /**
170
+ * Create a wrapped test function that runs beforeEach/afterEach hooks
171
+ *
172
+ * The hooks run with each benchmark iteration, matching test framework
173
+ * semantics where beforeEach runs before each test execution.
174
+ *
175
+ * Hook ordering follows standard test framework conventions:
176
+ *
177
+ * - BeforeEach: runs in declaration order (FIFO)
178
+ * - AfterEach: runs in reverse declaration order (LIFO)
179
+ */
180
+ const createWrappedTestFn = (
181
+ testFn: () => Promise<void> | void,
182
+ hooks: SuiteHooks,
183
+ ): (() => Promise<void> | void) => {
184
+ // If no per-test hooks, return original function
185
+ if (hooks.beforeEach.length === 0 && hooks.afterEach.length === 0) {
186
+ return testFn;
187
+ }
188
+
189
+ // Wrap with hooks
190
+ return async () => {
191
+ // Run beforeEach hooks in declaration order (FIFO)
192
+ for (const hook of hooks.beforeEach) {
193
+ await hook();
194
+ }
195
+
196
+ // Run test
197
+ await testFn();
198
+
199
+ // Run afterEach hooks in reverse order (LIFO), like most test frameworks
200
+ for (const hook of [...hooks.afterEach].reverse()) {
201
+ await hook();
202
+ }
203
+ };
204
+ };
205
+
206
+ /**
207
+ * Flatten nested suites into a list of [prefixed-name, wrapped-fn] pairs
208
+ */
209
+ const flattenSuiteTests = (
210
+ suite: CapturedSuite,
211
+ prefix: string,
212
+ ): Array<[string, () => Promise<void> | void]> => {
213
+ const results: Array<[string, () => Promise<void> | void]> = [];
214
+
215
+ // Add direct tests with prefix
216
+ for (const test of suite.tests) {
217
+ if (!test.skip) {
218
+ const wrappedFn = createWrappedTestFn(test.fn, suite.hooks);
219
+ results.push([`${prefix} > ${test.name}`, wrappedFn]);
220
+ }
221
+ }
222
+
223
+ // Recursively add nested suite tests
224
+ for (const child of suite.children) {
225
+ const childResults = flattenSuiteTests(child, `${prefix} > ${child.name}`);
226
+ results.push(...childResults);
227
+ }
228
+
229
+ return results;
230
+ };
231
+
232
+ /**
233
+ * Helper to build the suite record structure
234
+ */
235
+ const suiteToRecord = (suite: CapturedSuite) => {
236
+ const benchmarks: Record<string, { fn: () => Promise<void> | void }> = {};
237
+
238
+ // Add tests as benchmarks
239
+ for (const test of suite.tests) {
240
+ if (!test.skip) {
241
+ // Wrap test function with beforeEach/afterEach hooks
242
+ const wrappedFn = createWrappedTestFn(test.fn, suite.hooks);
243
+ benchmarks[test.name] = { fn: wrappedFn };
244
+ }
245
+ }
246
+
247
+ // Flatten nested suites into flat benchmark names
248
+ // e.g., "Parent > Child > test" becomes "Child > test"
249
+ for (const child of suite.children) {
250
+ const childBenchmarks = flattenSuiteTests(child, child.name);
251
+ for (const [name, fn] of childBenchmarks) {
252
+ benchmarks[name] = { fn };
253
+ }
254
+ }
255
+
256
+ // Combine before hooks into a single setup function
257
+ const setup =
258
+ suite.hooks.before.length > 0
259
+ ? async () => {
260
+ for (const hook of suite.hooks.before) {
261
+ await hook();
262
+ }
263
+ }
264
+ : undefined;
265
+
266
+ // Combine after hooks into a single teardown function
267
+ const teardown =
268
+ suite.hooks.after.length > 0
269
+ ? async () => {
270
+ for (const hook of suite.hooks.after) {
271
+ await hook();
272
+ }
273
+ }
274
+ : undefined;
275
+
276
+ return {
277
+ benchmarks,
278
+ ...(setup && { setup }),
279
+ ...(teardown && { teardown }),
280
+ };
281
+ };
@@ -17,6 +17,11 @@ import { createInterface } from 'node:readline';
17
17
 
18
18
  import type { CliContext } from '../index.js';
19
19
 
20
+ import {
21
+ DEFAULT_BENCHMARK_DIR,
22
+ DEFAULT_OUTPUT_DIR,
23
+ SITE_URL,
24
+ } from '../../constants.js';
20
25
  import {
21
26
  InvalidArgumentError,
22
27
  UnsupportedConfigFormatError,
@@ -43,40 +48,44 @@ const PROJECT_TEMPLATES = {
43
48
  advanced: {
44
49
  configOptions: {
45
50
  iterations: 1000,
46
- outputDir: '.modestbench',
47
- pattern: 'bench/**/*.bench.{js,ts}',
51
+ outputDir: DEFAULT_OUTPUT_DIR,
52
+ pattern: `${DEFAULT_BENCHMARK_DIR}/**/*.bench.{js,ts}`,
48
53
  reporters: ['human', 'json'],
49
54
  time: 10000,
50
55
  warmup: 50,
51
56
  },
52
57
  description: 'Feature-rich setup with multiple reporters and configuration',
53
- directories: ['bench', '.modestbench'],
58
+ directories: [DEFAULT_BENCHMARK_DIR, DEFAULT_OUTPUT_DIR],
54
59
  name: 'Advanced Project',
55
60
  },
56
61
  basic: {
57
62
  configOptions: {
58
63
  iterations: 100,
59
- outputDir: '.modestbench',
60
- pattern: 'bench/**/*.bench.{js,ts}',
64
+ outputDir: DEFAULT_OUTPUT_DIR,
65
+ pattern: `${DEFAULT_BENCHMARK_DIR}/**/*.bench.{js,ts}`,
61
66
  reporters: ['human'],
62
67
  time: 5000,
63
68
  },
64
69
  description: 'Simple benchmark setup for small projects',
65
- directories: ['bench', '.modestbench'],
70
+ directories: [DEFAULT_BENCHMARK_DIR, DEFAULT_OUTPUT_DIR],
66
71
  name: 'Basic Project',
67
72
  },
68
73
  library: {
69
74
  configOptions: {
70
75
  bail: false,
71
76
  iterations: 5000,
72
- outputDir: '.modestbench',
73
- pattern: 'bench/**/*.bench.{js,ts}',
77
+ outputDir: DEFAULT_OUTPUT_DIR,
78
+ pattern: `${DEFAULT_BENCHMARK_DIR}/**/*.bench.{js,ts}`,
74
79
  reporters: ['human', 'json'],
75
80
  time: 15000,
76
81
  warmup: 100,
77
82
  },
78
83
  description: 'Optimized for library performance testing',
79
- directories: ['bench', 'bench/suites', '.modestbench'],
84
+ directories: [
85
+ DEFAULT_BENCHMARK_DIR,
86
+ `${DEFAULT_BENCHMARK_DIR}/suites`,
87
+ DEFAULT_OUTPUT_DIR,
88
+ ],
80
89
  name: 'Library Project',
81
90
  },
82
91
  } as const;
@@ -375,7 +384,7 @@ const createAdditionalFiles = async (
375
384
  // Create README.md
376
385
  const readmeContent = `# Benchmark Project
377
386
 
378
- This project uses [ModestBench](https://github.com/your-org/modestbench) for performance testing.
387
+ This project uses [ModestBench](${SITE_URL}) for performance testing.
379
388
 
380
389
  ## Getting Started
381
390
 
@@ -386,7 +395,7 @@ modestbench run
386
395
 
387
396
  Run specific benchmarks:
388
397
  \`\`\`bash
389
- modestbench run "bench/array-*.bench.js"
398
+ modestbench run "${DEFAULT_BENCHMARK_DIR}/array-*.bench.js"
390
399
  \`\`\`
391
400
 
392
401
  View benchmark history:
@@ -400,7 +409,7 @@ See \`modestbench.config.*\` for benchmark configuration options.
400
409
 
401
410
  ## Writing Benchmarks
402
411
 
403
- Create new benchmark files in the \`bench/\` directory. See the examples for the expected format.
412
+ Create new benchmark files in the \`${DEFAULT_BENCHMARK_DIR}/\` directory. See the examples for the expected format.
404
413
  `;
405
414
 
406
415
  try {
@@ -423,7 +432,7 @@ const createOrUpdateGitignore = async (
423
432
  options?: { quiet?: boolean; yes?: boolean },
424
433
  ): Promise<void> => {
425
434
  const gitignorePath = resolve(cwd, '.gitignore');
426
- const modestbenchEntry = '.modestbench/';
435
+ const modestbenchEntry = `${DEFAULT_OUTPUT_DIR}/`;
427
436
  const modestbenchSection = `\n# ModestBench history\n${modestbenchEntry}\n`;
428
437
 
429
438
  try {
@@ -436,13 +445,13 @@ const createOrUpdateGitignore = async (
436
445
  }
437
446
 
438
447
  if (existingContent) {
439
- // File exists, check if .modestbench/ is already present
448
+ // File exists, check if ${DEFAULT_OUTPUT_DIR}/ is already present
440
449
  if (existingContent.includes(modestbenchEntry)) {
441
450
  // Already present, nothing to do
442
451
  return;
443
452
  }
444
453
 
445
- // Append .modestbench/ entry (--yes or --quiet means auto-accept)
454
+ // Append default output dir entry (--yes or --quiet means auto-accept)
446
455
  if (options?.yes || options?.quiet) {
447
456
  // Ensure content ends with newline
448
457
  const contentToAppend = existingContent.endsWith('\n')
@@ -464,7 +473,6 @@ build/
464
473
  coverage/
465
474
 
466
475
  # Benchmark output
467
- benchmark-results/
468
476
  ${modestbenchSection}`;
469
477
  await writeFile(gitignorePath, newContent, 'utf8');
470
478
  }
@@ -550,7 +558,7 @@ const createDirectories = async (
550
558
  * Create example benchmark files
551
559
  */
552
560
  const createExampleBenchmarks = async (cwd: string): Promise<void> => {
553
- const benchmarksDir = resolve(cwd, 'bench');
561
+ const benchmarksDir = resolve(cwd, DEFAULT_BENCHMARK_DIR);
554
562
 
555
563
  for (const [name, example] of Object.entries(EXAMPLE_BENCHMARKS)) {
556
564
  const filePath = join(benchmarksDir, example.filename);
@@ -273,10 +273,17 @@ const handleResults = (
273
273
  // Check if any files failed to load/execute
274
274
  const hasFileErrors = executionResult.files.some((file) => file.error);
275
275
 
276
+ // Check if any suites failed (e.g., setup errors)
277
+ const hasSuiteErrors = executionResult.files.some((file) =>
278
+ file.suites.some((suite) => suite.error),
279
+ );
280
+
276
281
  // Determine exit code based on results
277
282
  if (executionResult && executionResult.summary) {
278
- // Return error if there are failed tasks OR file-level errors
279
- return executionResult.summary.failedTasks > 0 || hasFileErrors
283
+ // Return error if there are failed tasks, file errors, OR suite errors
284
+ return executionResult.summary.failedTasks > 0 ||
285
+ hasFileErrors ||
286
+ hasSuiteErrors
280
287
  ? ExitCodes.BENCHMARK_FAILURES
281
288
  : ExitCodes.SUCCESS;
282
289
  }