modestbench 0.7.0 → 0.8.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 (88) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/run.cjs +89 -49
  3. package/dist/cli/commands/run.cjs.map +1 -1
  4. package/dist/cli/commands/run.d.cts.map +1 -1
  5. package/dist/cli/commands/run.d.ts.map +1 -1
  6. package/dist/cli/commands/run.js +90 -50
  7. package/dist/cli/commands/run.js.map +1 -1
  8. package/dist/constants.cjs +2 -0
  9. package/dist/constants.cjs.map +1 -1
  10. package/dist/constants.d.cts +2 -0
  11. package/dist/constants.d.cts.map +1 -1
  12. package/dist/constants.d.ts +2 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +2 -0
  15. package/dist/constants.js.map +1 -1
  16. package/dist/errors/index.cjs +3 -1
  17. package/dist/errors/index.cjs.map +1 -1
  18. package/dist/errors/index.d.cts +1 -1
  19. package/dist/errors/index.d.cts.map +1 -1
  20. package/dist/errors/index.d.ts +1 -1
  21. package/dist/errors/index.d.ts.map +1 -1
  22. package/dist/errors/index.js +1 -1
  23. package/dist/errors/index.js.map +1 -1
  24. package/dist/errors/reporter.cjs +45 -1
  25. package/dist/errors/reporter.cjs.map +1 -1
  26. package/dist/errors/reporter.d.cts +32 -0
  27. package/dist/errors/reporter.d.cts.map +1 -1
  28. package/dist/errors/reporter.d.ts +32 -0
  29. package/dist/errors/reporter.d.ts.map +1 -1
  30. package/dist/errors/reporter.js +42 -0
  31. package/dist/errors/reporter.js.map +1 -1
  32. package/dist/index.cjs +16 -1
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +3 -1
  35. package/dist/index.d.cts.map +1 -1
  36. package/dist/index.d.ts +3 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +5 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/services/reporter-loader.cjs +281 -0
  41. package/dist/services/reporter-loader.cjs.map +1 -0
  42. package/dist/services/reporter-loader.d.cts +67 -0
  43. package/dist/services/reporter-loader.d.cts.map +1 -0
  44. package/dist/services/reporter-loader.d.ts +67 -0
  45. package/dist/services/reporter-loader.d.ts.map +1 -0
  46. package/dist/services/reporter-loader.js +241 -0
  47. package/dist/services/reporter-loader.js.map +1 -0
  48. package/dist/types/index.cjs.map +1 -1
  49. package/dist/types/index.d.cts +1 -0
  50. package/dist/types/index.d.cts.map +1 -1
  51. package/dist/types/index.d.ts +1 -0
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/index.js.map +1 -1
  54. package/dist/types/plugin.cjs +9 -0
  55. package/dist/types/plugin.cjs.map +1 -0
  56. package/dist/types/plugin.d.cts +179 -0
  57. package/dist/types/plugin.d.cts.map +1 -0
  58. package/dist/types/plugin.d.ts +179 -0
  59. package/dist/types/plugin.d.ts.map +1 -0
  60. package/dist/types/plugin.js +8 -0
  61. package/dist/types/plugin.js.map +1 -0
  62. package/dist/utils/package.cjs +66 -5
  63. package/dist/utils/package.cjs.map +1 -1
  64. package/dist/utils/package.d.cts +6 -0
  65. package/dist/utils/package.d.cts.map +1 -1
  66. package/dist/utils/package.d.ts +6 -0
  67. package/dist/utils/package.d.ts.map +1 -1
  68. package/dist/utils/package.js +31 -1
  69. package/dist/utils/package.js.map +1 -1
  70. package/dist/utils/reporter-utils.cjs +90 -0
  71. package/dist/utils/reporter-utils.cjs.map +1 -0
  72. package/dist/utils/reporter-utils.d.cts +42 -0
  73. package/dist/utils/reporter-utils.d.cts.map +1 -0
  74. package/dist/utils/reporter-utils.d.ts +42 -0
  75. package/dist/utils/reporter-utils.d.ts.map +1 -0
  76. package/dist/utils/reporter-utils.js +83 -0
  77. package/dist/utils/reporter-utils.js.map +1 -0
  78. package/package.json +4 -4
  79. package/src/cli/commands/run.ts +127 -68
  80. package/src/constants.ts +2 -0
  81. package/src/errors/index.ts +2 -0
  82. package/src/errors/reporter.ts +55 -0
  83. package/src/index.ts +19 -1
  84. package/src/services/reporter-loader.ts +323 -0
  85. package/src/types/index.ts +3 -0
  86. package/src/types/plugin.ts +197 -0
  87. package/src/utils/package.ts +32 -1
  88. package/src/utils/reporter-utils.ts +85 -0
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * ModestBench Reporter Utilities
4
+ *
5
+ * Formatting functions for benchmark data, exported for use by third-party
6
+ * reporter plugins.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.reporterUtils = exports.formatPercentage = exports.formatOpsPerSecond = exports.formatDuration = exports.formatBytes = void 0;
10
+ /**
11
+ * Format bytes in human-readable format
12
+ *
13
+ * @param bytes - Number of bytes
14
+ * @returns Formatted string (e.g., "1.5 GB", "256 MB", "1.2 KB")
15
+ */
16
+ const formatBytes = (bytes) => {
17
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
18
+ let size = bytes;
19
+ let unitIndex = 0;
20
+ while (size >= 1024 && unitIndex < units.length - 1) {
21
+ size /= 1024;
22
+ unitIndex++;
23
+ }
24
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
25
+ };
26
+ exports.formatBytes = formatBytes;
27
+ /**
28
+ * Format duration in human-readable format
29
+ *
30
+ * @param nanoseconds - Duration in nanoseconds
31
+ * @returns Formatted string (e.g., "1.23ms", "456.78μs", "789.00ns")
32
+ */
33
+ const formatDuration = (nanoseconds) => {
34
+ if (nanoseconds < 1000) {
35
+ return `${nanoseconds.toFixed(2)}ns`;
36
+ }
37
+ else if (nanoseconds < 1_000_000) {
38
+ return `${(nanoseconds / 1000).toFixed(2)}μs`;
39
+ }
40
+ else if (nanoseconds < 1_000_000_000) {
41
+ return `${(nanoseconds / 1_000_000).toFixed(2)}ms`;
42
+ }
43
+ else {
44
+ return `${(nanoseconds / 1_000_000_000).toFixed(2)}s`;
45
+ }
46
+ };
47
+ exports.formatDuration = formatDuration;
48
+ /**
49
+ * Format operations per second in human-readable format
50
+ *
51
+ * @param opsPerSecond - Operations per second
52
+ * @returns Formatted string (e.g., "1.2M ops/sec", "456K ops/sec")
53
+ */
54
+ const formatOpsPerSecond = (opsPerSecond) => {
55
+ if (opsPerSecond < 1000) {
56
+ return `${opsPerSecond.toFixed(2)} ops/sec`;
57
+ }
58
+ else if (opsPerSecond < 1_000_000) {
59
+ return `${(opsPerSecond / 1000).toFixed(2)}K ops/sec`;
60
+ }
61
+ else if (opsPerSecond < 1_000_000_000) {
62
+ return `${(opsPerSecond / 1_000_000).toFixed(2)}M ops/sec`;
63
+ }
64
+ else {
65
+ return `${(opsPerSecond / 1_000_000_000).toFixed(2)}B ops/sec`;
66
+ }
67
+ };
68
+ exports.formatOpsPerSecond = formatOpsPerSecond;
69
+ /**
70
+ * Format percentage value
71
+ *
72
+ * @param value - Percentage value (e.g., 12.345 for 12.345%)
73
+ * @returns Formatted string (e.g., "12.35%")
74
+ */
75
+ const formatPercentage = (value) => {
76
+ return `${value.toFixed(2)}%`;
77
+ };
78
+ exports.formatPercentage = formatPercentage;
79
+ /**
80
+ * Reporter utilities object implementing the ReporterUtils interface
81
+ *
82
+ * This object is provided to reporter plugins via the ReporterContext.
83
+ */
84
+ exports.reporterUtils = {
85
+ formatBytes: exports.formatBytes,
86
+ formatDuration: exports.formatDuration,
87
+ formatOpsPerSecond: exports.formatOpsPerSecond,
88
+ formatPercentage: exports.formatPercentage,
89
+ };
90
+ //# sourceMappingURL=reporter-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-utils.js","sourceRoot":"","sources":["../../src/utils/reporter-utils.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAIH;;;;;GAKG;AACI,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE;IACnD,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,IAAI,IAAI,CAAC;QACb,SAAS,EAAE,CAAC;IACd,CAAC;IAED,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AAClD,CAAC,CAAC;AAXW,QAAA,WAAW,eAWtB;AAEF;;;;;GAKG;AACI,MAAM,cAAc,GAAG,CAAC,WAAmB,EAAU,EAAE;IAC5D,IAAI,WAAW,GAAG,IAAI,EAAE,CAAC;QACvB,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACvC,CAAC;SAAM,IAAI,WAAW,GAAG,SAAS,EAAE,CAAC;QACnC,OAAO,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,CAAC;SAAM,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;QACvC,OAAO,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,WAAW,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,CAAC;AACH,CAAC,CAAC;AAVW,QAAA,cAAc,kBAUzB;AAEF;;;;;GAKG;AACI,MAAM,kBAAkB,GAAG,CAAC,YAAoB,EAAU,EAAE;IACjE,IAAI,YAAY,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9C,CAAC;SAAM,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;QACpC,OAAO,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IACxD,CAAC;SAAM,IAAI,YAAY,GAAG,aAAa,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IACjE,CAAC;AACH,CAAC,CAAC;AAVW,QAAA,kBAAkB,sBAU7B;AAEF;;;;;GAKG;AACI,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAU,EAAE;IACxD,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAChC,CAAC,CAAC;AAFW,QAAA,gBAAgB,oBAE3B;AAEF;;;;GAIG;AACU,QAAA,aAAa,GAAkB;IAC1C,WAAW,EAAX,mBAAW;IACX,cAAc,EAAd,sBAAc;IACd,kBAAkB,EAAlB,0BAAkB;IAClB,gBAAgB,EAAhB,wBAAgB;CACjB,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ModestBench Reporter Utilities
3
+ *
4
+ * Formatting functions for benchmark data, exported for use by third-party
5
+ * reporter plugins.
6
+ */
7
+ import type { ReporterUtils } from "../types/plugin.cjs";
8
+ /**
9
+ * Format bytes in human-readable format
10
+ *
11
+ * @param bytes - Number of bytes
12
+ * @returns Formatted string (e.g., "1.5 GB", "256 MB", "1.2 KB")
13
+ */
14
+ export declare const formatBytes: (bytes: number) => string;
15
+ /**
16
+ * Format duration in human-readable format
17
+ *
18
+ * @param nanoseconds - Duration in nanoseconds
19
+ * @returns Formatted string (e.g., "1.23ms", "456.78μs", "789.00ns")
20
+ */
21
+ export declare const formatDuration: (nanoseconds: number) => string;
22
+ /**
23
+ * Format operations per second in human-readable format
24
+ *
25
+ * @param opsPerSecond - Operations per second
26
+ * @returns Formatted string (e.g., "1.2M ops/sec", "456K ops/sec")
27
+ */
28
+ export declare const formatOpsPerSecond: (opsPerSecond: number) => string;
29
+ /**
30
+ * Format percentage value
31
+ *
32
+ * @param value - Percentage value (e.g., 12.345 for 12.345%)
33
+ * @returns Formatted string (e.g., "12.35%")
34
+ */
35
+ export declare const formatPercentage: (value: number) => string;
36
+ /**
37
+ * Reporter utilities object implementing the ReporterUtils interface
38
+ *
39
+ * This object is provided to reporter plugins via the ReporterContext.
40
+ */
41
+ export declare const reporterUtils: ReporterUtils;
42
+ //# sourceMappingURL=reporter-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-utils.d.ts","sourceRoot":"","sources":["../../src/utils/reporter-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,4BAA2B;AAExD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAW3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,KAAG,MAUpD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MAUzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAEhD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ModestBench Reporter Utilities
3
+ *
4
+ * Formatting functions for benchmark data, exported for use by third-party
5
+ * reporter plugins.
6
+ */
7
+ import type { ReporterUtils } from "../types/plugin.js";
8
+ /**
9
+ * Format bytes in human-readable format
10
+ *
11
+ * @param bytes - Number of bytes
12
+ * @returns Formatted string (e.g., "1.5 GB", "256 MB", "1.2 KB")
13
+ */
14
+ export declare const formatBytes: (bytes: number) => string;
15
+ /**
16
+ * Format duration in human-readable format
17
+ *
18
+ * @param nanoseconds - Duration in nanoseconds
19
+ * @returns Formatted string (e.g., "1.23ms", "456.78μs", "789.00ns")
20
+ */
21
+ export declare const formatDuration: (nanoseconds: number) => string;
22
+ /**
23
+ * Format operations per second in human-readable format
24
+ *
25
+ * @param opsPerSecond - Operations per second
26
+ * @returns Formatted string (e.g., "1.2M ops/sec", "456K ops/sec")
27
+ */
28
+ export declare const formatOpsPerSecond: (opsPerSecond: number) => string;
29
+ /**
30
+ * Format percentage value
31
+ *
32
+ * @param value - Percentage value (e.g., 12.345 for 12.345%)
33
+ * @returns Formatted string (e.g., "12.35%")
34
+ */
35
+ export declare const formatPercentage: (value: number) => string;
36
+ /**
37
+ * Reporter utilities object implementing the ReporterUtils interface
38
+ *
39
+ * This object is provided to reporter plugins via the ReporterContext.
40
+ */
41
+ export declare const reporterUtils: ReporterUtils;
42
+ //# sourceMappingURL=reporter-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-utils.d.ts","sourceRoot":"","sources":["../../src/utils/reporter-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,2BAA2B;AAExD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAW3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,KAAG,MAUpD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MAUzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAEhD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * ModestBench Reporter Utilities
3
+ *
4
+ * Formatting functions for benchmark data, exported for use by third-party
5
+ * reporter plugins.
6
+ */
7
+ /**
8
+ * Format bytes in human-readable format
9
+ *
10
+ * @param bytes - Number of bytes
11
+ * @returns Formatted string (e.g., "1.5 GB", "256 MB", "1.2 KB")
12
+ */
13
+ export const formatBytes = (bytes) => {
14
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
15
+ let size = bytes;
16
+ let unitIndex = 0;
17
+ while (size >= 1024 && unitIndex < units.length - 1) {
18
+ size /= 1024;
19
+ unitIndex++;
20
+ }
21
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
22
+ };
23
+ /**
24
+ * Format duration in human-readable format
25
+ *
26
+ * @param nanoseconds - Duration in nanoseconds
27
+ * @returns Formatted string (e.g., "1.23ms", "456.78μs", "789.00ns")
28
+ */
29
+ export const formatDuration = (nanoseconds) => {
30
+ if (nanoseconds < 1000) {
31
+ return `${nanoseconds.toFixed(2)}ns`;
32
+ }
33
+ else if (nanoseconds < 1_000_000) {
34
+ return `${(nanoseconds / 1000).toFixed(2)}μs`;
35
+ }
36
+ else if (nanoseconds < 1_000_000_000) {
37
+ return `${(nanoseconds / 1_000_000).toFixed(2)}ms`;
38
+ }
39
+ else {
40
+ return `${(nanoseconds / 1_000_000_000).toFixed(2)}s`;
41
+ }
42
+ };
43
+ /**
44
+ * Format operations per second in human-readable format
45
+ *
46
+ * @param opsPerSecond - Operations per second
47
+ * @returns Formatted string (e.g., "1.2M ops/sec", "456K ops/sec")
48
+ */
49
+ export const formatOpsPerSecond = (opsPerSecond) => {
50
+ if (opsPerSecond < 1000) {
51
+ return `${opsPerSecond.toFixed(2)} ops/sec`;
52
+ }
53
+ else if (opsPerSecond < 1_000_000) {
54
+ return `${(opsPerSecond / 1000).toFixed(2)}K ops/sec`;
55
+ }
56
+ else if (opsPerSecond < 1_000_000_000) {
57
+ return `${(opsPerSecond / 1_000_000).toFixed(2)}M ops/sec`;
58
+ }
59
+ else {
60
+ return `${(opsPerSecond / 1_000_000_000).toFixed(2)}B ops/sec`;
61
+ }
62
+ };
63
+ /**
64
+ * Format percentage value
65
+ *
66
+ * @param value - Percentage value (e.g., 12.345 for 12.345%)
67
+ * @returns Formatted string (e.g., "12.35%")
68
+ */
69
+ export const formatPercentage = (value) => {
70
+ return `${value.toFixed(2)}%`;
71
+ };
72
+ /**
73
+ * Reporter utilities object implementing the ReporterUtils interface
74
+ *
75
+ * This object is provided to reporter plugins via the ReporterContext.
76
+ */
77
+ export const reporterUtils = {
78
+ formatBytes,
79
+ formatDuration,
80
+ formatOpsPerSecond,
81
+ formatPercentage,
82
+ };
83
+ //# sourceMappingURL=reporter-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-utils.js","sourceRoot":"","sources":["../../src/utils/reporter-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE;IACnD,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,IAAI,IAAI,CAAC;QACb,SAAS,EAAE,CAAC;IACd,CAAC;IAED,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AAClD,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,WAAmB,EAAU,EAAE;IAC5D,IAAI,WAAW,GAAG,IAAI,EAAE,CAAC;QACvB,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACvC,CAAC;SAAM,IAAI,WAAW,GAAG,SAAS,EAAE,CAAC;QACnC,OAAO,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,CAAC;SAAM,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;QACvC,OAAO,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,WAAW,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAoB,EAAU,EAAE;IACjE,IAAI,YAAY,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9C,CAAC;SAAM,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;QACpC,OAAO,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IACxD,CAAC;SAAM,IAAI,YAAY,GAAG,aAAa,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;IACjE,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAU,EAAE;IACxD,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAChC,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,WAAW;IACX,cAAc;IACd,kBAAkB;IAClB,gBAAgB;CACjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modestbench",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "A full-ass benchmarking framework for Node.js",
6
6
  "repository": {
@@ -94,7 +94,7 @@
94
94
  "clean": "rm -rf dist",
95
95
  "commitlint": "commitlint",
96
96
  "dev": "zshy --watch",
97
- "docs:build": "run-s docs:copy astro:build",
97
+ "docs:build": "run-s build:schema docs:copy astro:build",
98
98
  "docs:copy": "tsx scripts/copy-docs.ts",
99
99
  "docs:dev": "run-s docs:copy astro:dev",
100
100
  "docs:preview": "astro preview --config astro.config.js",
@@ -144,7 +144,7 @@
144
144
  "@types/node": "24.10.3",
145
145
  "@types/wallabyjs": "0.0.15",
146
146
  "@types/yargs": "17.0.35",
147
- "asciinema-player": "3.12.1",
147
+ "asciinema-player": "3.13.2",
148
148
  "astro": "5.16.5",
149
149
  "astro-broken-link-checker": "file:./vendor/astro-broken-link-checker",
150
150
  "ava": "6.4.1",
@@ -156,7 +156,7 @@
156
156
  "globals": "16.5.0",
157
157
  "husky": "9.1.7",
158
158
  "jest": "30.2.0",
159
- "knip": "5.73.1",
159
+ "knip": "5.73.4",
160
160
  "lint-staged": "16.2.7",
161
161
  "markdownlint-cli2": "0.20.0",
162
162
  "markdownlint-cli2-formatter-pretty": "0.0.9",
@@ -7,7 +7,11 @@
7
7
 
8
8
  import { resolve } from 'node:path';
9
9
 
10
- import type { BenchmarkRun, ModestBenchConfig } from '../../types/index.js';
10
+ import type {
11
+ BenchmarkRun,
12
+ ModestBenchConfig,
13
+ Reporter,
14
+ } from '../../types/index.js';
11
15
  import type { CliContext } from '../index.js';
12
16
 
13
17
  import { ErrorCodes, ExitCodes } from '../../constants.js';
@@ -15,6 +19,8 @@ import { resolveOutputPath } from '../../core/output-path-resolver.js';
15
19
  import {
16
20
  type BudgetExceededError,
17
21
  InvalidArgumentError,
22
+ ReporterLoadError,
23
+ ReporterValidationError,
18
24
  UnknownReporterError,
19
25
  } from '../../errors/index.js';
20
26
  import { CsvReporter } from '../../reporters/csv.js';
@@ -22,6 +28,11 @@ import { HumanReporter } from '../../reporters/human.js';
22
28
  import { JsonReporter } from '../../reporters/json.js';
23
29
  import { NyanReporter } from '../../reporters/nyan.js';
24
30
  import { SimpleReporter } from '../../reporters/simple.js';
31
+ import {
32
+ isBuiltInReporter,
33
+ isFilePath,
34
+ loadReporter,
35
+ } from '../../services/reporter-loader.js';
25
36
  import { hasErrorCode, isError } from '../../utils/type-guards.js';
26
37
 
27
38
  /**
@@ -106,7 +117,7 @@ export const handleRunCommand = async (
106
117
  if (showCliMessages) {
107
118
  console.error('Setting up reporters...');
108
119
  }
109
- const reporters = setupReporters(
120
+ const reporters = await setupReporters(
110
121
  context,
111
122
  config,
112
123
  verbose,
@@ -370,19 +381,26 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
370
381
 
371
382
  /**
372
383
  * Setup and configure reporters based on configuration
384
+ *
385
+ * Supports built-in reporters, registry-based custom reporters, and external
386
+ * reporters loaded from file paths or npm packages.
373
387
  */
374
- const setupReporters = (
388
+ const setupReporters = async (
375
389
  context: CliContext,
376
- config: { outputDir?: string; reporters?: string[] },
390
+ config: {
391
+ outputDir?: string;
392
+ reporterConfig?: Record<string, unknown>;
393
+ reporters?: string[];
394
+ },
377
395
  isVerbose: boolean,
378
396
  showCliMessages: boolean,
379
397
  explicitQuiet: boolean,
380
398
  explicitOutputDir?: string,
381
399
  explicitOutputFile?: string,
382
400
  progressOption?: boolean,
383
- ) => {
401
+ ): Promise<Reporter[]> => {
384
402
  try {
385
- const reporters = [];
403
+ const reporters: Reporter[] = [];
386
404
  // Dedupe requested reporters
387
405
  const requestedReporters = [...new Set(config.reporters || ['human'])];
388
406
 
@@ -398,67 +416,103 @@ const setupReporters = (
398
416
  const builtInReporters = ['human', 'json', 'csv', 'nyan', 'simple'];
399
417
 
400
418
  for (const reporterName of requestedReporters) {
401
- let reporter;
402
-
403
- // Create reporter instances with output path configuration
404
- switch (reporterName) {
405
- case 'csv': {
406
- const outputPath = resolveOutputPath(
407
- outputDir,
408
- explicitOutputFile,
409
- 'results.csv',
410
- );
411
- reporter = new CsvReporter({
412
- includeHeaders: true,
413
- includeMetadata: true,
414
- ...(outputPath ? { outputPath } : {}),
415
- quiet: explicitQuiet, // Only applies explicit --quiet flag; CSV output can coexist with progress messages on different streams
416
- verbose: isVerbose,
417
- });
418
- break;
419
- }
419
+ let reporter: Reporter;
420
+
421
+ // Check if this is a built-in reporter
422
+ if (isBuiltInReporter(reporterName)) {
423
+ // Create reporter instances with output path configuration
424
+ switch (reporterName) {
425
+ case 'csv': {
426
+ const outputPath = resolveOutputPath(
427
+ outputDir,
428
+ explicitOutputFile,
429
+ 'results.csv',
430
+ );
431
+ reporter = new CsvReporter({
432
+ includeHeaders: true,
433
+ includeMetadata: true,
434
+ ...(outputPath ? { outputPath } : {}),
435
+ quiet: explicitQuiet,
436
+ verbose: isVerbose,
437
+ });
438
+ break;
439
+ }
420
440
 
421
- case 'human':
422
- reporter = new HumanReporter({
423
- color: true,
424
- progress: progressOption ?? true,
425
- quiet: explicitQuiet, // Only applies explicit --quiet flag; JSON reporter forcing quiet mode does not affect HumanReporter progress output
426
- verbose: isVerbose,
427
- });
428
- break;
429
-
430
- case 'json': {
431
- const outputPath = resolveOutputPath(
432
- outputDir,
433
- explicitOutputFile,
434
- 'results.json',
435
- );
436
- reporter = new JsonReporter({
437
- ...(outputPath ? { outputPath } : {}),
438
- prettyPrint: true,
439
- });
440
- break;
441
- }
441
+ case 'human':
442
+ reporter = new HumanReporter({
443
+ color: true,
444
+ progress: progressOption ?? true,
445
+ quiet: explicitQuiet,
446
+ verbose: isVerbose,
447
+ });
448
+ break;
449
+
450
+ case 'json': {
451
+ const outputPath = resolveOutputPath(
452
+ outputDir,
453
+ explicitOutputFile,
454
+ 'results.json',
455
+ );
456
+ reporter = new JsonReporter({
457
+ ...(outputPath ? { outputPath } : {}),
458
+ prettyPrint: true,
459
+ });
460
+ break;
461
+ }
442
462
 
443
- case 'nyan':
444
- reporter = new NyanReporter({
445
- color: true,
446
- quiet: explicitQuiet,
447
- });
448
- break;
449
-
450
- case 'simple':
451
- reporter = new SimpleReporter({
452
- quiet: explicitQuiet,
453
- verbose: isVerbose,
454
- });
455
- break;
456
-
457
- default:
458
- // Fall back to registry for custom reporters
459
- reporter = context.reporterRegistry.get(reporterName);
460
- if (!reporter) {
461
- // Combine built-in reporters with registered custom reporters
463
+ case 'nyan':
464
+ reporter = new NyanReporter({
465
+ color: true,
466
+ quiet: explicitQuiet,
467
+ });
468
+ break;
469
+
470
+ case 'simple':
471
+ reporter = new SimpleReporter({
472
+ quiet: explicitQuiet,
473
+ verbose: isVerbose,
474
+ });
475
+ break;
476
+
477
+ default:
478
+ // TypeScript exhaustiveness check - should never reach here
479
+ throw new Error(`Unhandled built-in reporter: ${reporterName}`);
480
+ }
481
+ } else if (isFilePath(reporterName)) {
482
+ // External reporter from file path
483
+ const reporterOptions =
484
+ (config.reporterConfig?.[reporterName] as Record<string, unknown>) ??
485
+ {};
486
+ if (showCliMessages) {
487
+ console.error(`Loading external reporter: ${reporterName}`);
488
+ }
489
+ reporter = await loadReporter(reporterName, reporterOptions);
490
+ } else {
491
+ // Try registry first, then npm package
492
+ const registryReporter = context.reporterRegistry.get(reporterName);
493
+ if (registryReporter) {
494
+ reporter = registryReporter;
495
+ } else {
496
+ // Try loading as npm package
497
+ const reporterOptions =
498
+ (config.reporterConfig?.[reporterName] as Record<
499
+ string,
500
+ unknown
501
+ >) ?? {};
502
+ if (showCliMessages) {
503
+ console.error(`Loading reporter package: ${reporterName}`);
504
+ }
505
+ try {
506
+ reporter = await loadReporter(reporterName, reporterOptions);
507
+ } catch (error) {
508
+ // If loading fails and it's not a file path, provide helpful error
509
+ if (
510
+ error instanceof ReporterLoadError ||
511
+ error instanceof ReporterValidationError
512
+ ) {
513
+ throw error;
514
+ }
515
+ // Combine built-in reporters with registered custom reporters for error message
462
516
  const registeredReporters = Object.keys(
463
517
  context.reporterRegistry.getAll(),
464
518
  );
@@ -467,10 +521,11 @@ const setupReporters = (
467
521
  ...registeredReporters,
468
522
  ];
469
523
  throw new UnknownReporterError(
470
- `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
524
+ `Unknown reporter: ${reporterName}. Available built-in reporters: ${availableReporters.join(', ')}. ` +
525
+ `For external reporters, use a file path (./my-reporter.js) or npm package name.`,
471
526
  );
472
527
  }
473
- break;
528
+ }
474
529
  }
475
530
 
476
531
  reporters.push(reporter);
@@ -484,7 +539,11 @@ const setupReporters = (
484
539
  } catch (error) {
485
540
  // Re-throw our custom errors
486
541
  const errorCode = hasErrorCode(error) ? error.code : undefined;
487
- if (errorCode === ErrorCodes.REPORTER_UNKNOWN) {
542
+ if (
543
+ errorCode === ErrorCodes.REPORTER_UNKNOWN ||
544
+ errorCode === ErrorCodes.REPORTER_LOAD_FAILED ||
545
+ errorCode === ErrorCodes.REPORTER_INVALID
546
+ ) {
488
547
  throw error;
489
548
  }
490
549
  throw new InvalidArgumentError(
package/src/constants.ts CHANGED
@@ -110,6 +110,8 @@ export const ErrorCodes = {
110
110
 
111
111
  //#region reporter-errors
112
112
  REPORTER_ALREADY_REGISTERED: 'ERR_MB_REPORTER_ALREADY_REGISTERED',
113
+ REPORTER_INVALID: 'ERR_MB_REPORTER_INVALID',
114
+ REPORTER_LOAD_FAILED: 'ERR_MB_REPORTER_LOAD_FAILED',
113
115
  REPORTER_OUTPUT_FAILED: 'ERR_MB_REPORTER_OUTPUT_FAILED',
114
116
  REPORTER_UNKNOWN: 'ERR_MB_REPORTER_UNKNOWN',
115
117
  //#endregion
@@ -53,7 +53,9 @@ export {
53
53
  // Reporter errors
54
54
  export {
55
55
  ReporterAlreadyRegisteredError,
56
+ ReporterLoadError,
56
57
  ReporterOutputError,
58
+ ReporterValidationError,
57
59
  UnknownReporterError,
58
60
  } from './reporter.js';
59
61
 
@@ -16,6 +16,26 @@ export class ReporterAlreadyRegisteredError extends ModestBenchError {
16
16
  readonly code = 'ERR_MB_REPORTER_ALREADY_REGISTERED';
17
17
  }
18
18
 
19
+ /**
20
+ * Reporter load failed
21
+ *
22
+ * Thrown when a reporter module cannot be loaded (file not found, syntax error,
23
+ * invalid module format, etc.).
24
+ */
25
+ export class ReporterLoadError extends ModestBenchError {
26
+ readonly code = 'ERR_MB_REPORTER_LOAD_FAILED';
27
+
28
+ /**
29
+ * The specifier (file path or package name) that failed to load
30
+ */
31
+ readonly specifier: string;
32
+
33
+ constructor(message: string, specifier: string, options?: ErrorOptions) {
34
+ super(`Failed to load reporter "${specifier}": ${message}`, options);
35
+ this.specifier = specifier;
36
+ }
37
+ }
38
+
19
39
  /**
20
40
  * Reporter output failed
21
41
  *
@@ -25,6 +45,41 @@ export class ReporterOutputError extends ModestBenchError {
25
45
  readonly code = 'ERR_MB_REPORTER_OUTPUT_FAILED';
26
46
  }
27
47
 
48
+ /**
49
+ * Reporter validation failed
50
+ *
51
+ * Thrown when a loaded module does not implement the required Reporter
52
+ * interface methods.
53
+ */
54
+ export class ReporterValidationError extends ModestBenchError {
55
+ readonly code = 'ERR_MB_REPORTER_INVALID';
56
+
57
+ /**
58
+ * The methods that are missing from the reporter
59
+ */
60
+ readonly missingMethods: string[];
61
+
62
+ /**
63
+ * The specifier (file path or package name) of the invalid reporter
64
+ */
65
+ readonly specifier: string;
66
+
67
+ constructor(
68
+ message: string,
69
+ specifier: string,
70
+ missingMethods: string[] = [],
71
+ options?: ErrorOptions,
72
+ ) {
73
+ const methodsInfo =
74
+ missingMethods.length > 0
75
+ ? ` Missing required methods: ${missingMethods.join(', ')}.`
76
+ : '';
77
+ super(`Invalid reporter "${specifier}": ${message}${methodsInfo}`, options);
78
+ this.specifier = specifier;
79
+ this.missingMethods = missingMethods;
80
+ }
81
+ }
82
+
28
83
  /**
29
84
  * Unknown reporter
30
85
  *