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,281 @@
1
+ "use strict";
2
+ /**
3
+ * ModestBench Reporter Loader
4
+ *
5
+ * Service for loading third-party reporter plugins from file paths or npm
6
+ * packages. Supports multiple export patterns: plain objects, classes, and
7
+ * factory functions (sync or async).
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
43
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
44
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
45
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
46
+ });
47
+ }
48
+ return path;
49
+ };
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.loadReporter = exports.isFilePath = exports.isBuiltInReporter = exports.createReporterContext = exports.PLUGIN_API_VERSION = void 0;
52
+ const node_path_1 = require("node:path");
53
+ const node_url_1 = require("node:url");
54
+ const constants_js_1 = require("../constants.cjs");
55
+ const reporter_js_1 = require("../errors/reporter.cjs");
56
+ const package_js_1 = require("../utils/package.cjs");
57
+ const reporter_utils_js_1 = require("../utils/reporter-utils.cjs");
58
+ /**
59
+ * Current plugin API version
60
+ *
61
+ * Increment this when making breaking changes to the plugin API.
62
+ */
63
+ exports.PLUGIN_API_VERSION = 1;
64
+ /**
65
+ * Set of built-in reporter names
66
+ */
67
+ const BUILT_IN_REPORTERS = new Set(Object.values(constants_js_1.Reporters));
68
+ /**
69
+ * Required methods that all reporters must implement
70
+ */
71
+ const REQUIRED_REPORTER_METHODS = [
72
+ 'onStart',
73
+ 'onEnd',
74
+ 'onError',
75
+ 'onTaskResult',
76
+ ];
77
+ /**
78
+ * Default logger implementation using console
79
+ *
80
+ * This provides a simple console-based logger for reporter plugins.
81
+ */
82
+ const defaultLogger = {
83
+ debug: (message, ...args) => console.debug(message, ...args),
84
+ error: (message, ...args) => console.error(message, ...args),
85
+ info: (message, ...args) => console.info(message, ...args),
86
+ trace: (message, ...args) => console.trace(message, ...args),
87
+ warn: (message, ...args) => console.warn(message, ...args),
88
+ };
89
+ /**
90
+ * Create a ReporterContext for passing to plugins
91
+ *
92
+ * @param logger - Optional logger to use (defaults to console-based logger)
93
+ * @returns ReporterContext with version info and utilities
94
+ */
95
+ const createReporterContext = (logger) => {
96
+ return {
97
+ logger: logger ?? defaultLogger,
98
+ pluginApiVersion: exports.PLUGIN_API_VERSION,
99
+ utils: reporter_utils_js_1.reporterUtils,
100
+ version: (0, package_js_1.getPackageVersion)(),
101
+ };
102
+ };
103
+ exports.createReporterContext = createReporterContext;
104
+ /**
105
+ * Get the list of missing required methods from a reporter object
106
+ *
107
+ * @param obj - Object to check
108
+ * @returns Array of missing method names
109
+ */
110
+ const getMissingMethods = (obj) => {
111
+ if (typeof obj !== 'object' || obj === null) {
112
+ return [...REQUIRED_REPORTER_METHODS];
113
+ }
114
+ return REQUIRED_REPORTER_METHODS.filter((method) => typeof obj[method] !== 'function');
115
+ };
116
+ /**
117
+ * Check if a specifier refers to a built-in reporter
118
+ *
119
+ * @param specifier - Reporter name or path
120
+ * @returns True if the specifier is a built-in reporter name
121
+ */
122
+ const isBuiltInReporter = (specifier) => {
123
+ return BUILT_IN_REPORTERS.has(specifier);
124
+ };
125
+ exports.isBuiltInReporter = isBuiltInReporter;
126
+ /**
127
+ * Check if a function is a class constructor
128
+ *
129
+ * Uses heuristics to distinguish classes from regular functions:
130
+ *
131
+ * - Classes have a non-writable prototype property
132
+ * - Class syntax produces different toString() output
133
+ *
134
+ * @param func - Function to check
135
+ * @returns True if the function appears to be a class constructor
136
+ */
137
+ const isClass = (func) => {
138
+ if (typeof func !== 'function') {
139
+ return false;
140
+ }
141
+ // Classes have a non-writable prototype
142
+ const protoDescriptor = Object.getOwnPropertyDescriptor(func, 'prototype');
143
+ if (!protoDescriptor || protoDescriptor.writable) {
144
+ return false;
145
+ }
146
+ // Check if it uses class syntax (handles both 'class Foo' and 'class{')
147
+ const funcStr = func.toString();
148
+ return /^class\b/.test(funcStr);
149
+ };
150
+ /**
151
+ * Check if a specifier looks like a file path
152
+ *
153
+ * @param specifier - Reporter name or path
154
+ * @returns True if the specifier appears to be a file path
155
+ */
156
+ const isFilePath = (specifier) => {
157
+ return (specifier.startsWith('.') ||
158
+ specifier.startsWith('/') ||
159
+ // isAbsolute handles Windows paths like 'C:\path\to\file.js'
160
+ (0, node_path_1.isAbsolute)(specifier));
161
+ };
162
+ exports.isFilePath = isFilePath;
163
+ /**
164
+ * Check if an object implements the Reporter interface
165
+ *
166
+ * Validates that all required methods are present and are functions.
167
+ *
168
+ * @param obj - Object to validate
169
+ * @returns True if the object has all required reporter methods
170
+ */
171
+ const isReporterObject = (obj) => {
172
+ if (typeof obj !== 'object' || obj === null) {
173
+ return false;
174
+ }
175
+ return REQUIRED_REPORTER_METHODS.every((method) => typeof obj[method] === 'function');
176
+ };
177
+ /**
178
+ * Load a reporter from a file path or npm package name
179
+ *
180
+ * Supports multiple export patterns:
181
+ *
182
+ * 1. Plain Reporter object (simplest, no options support)
183
+ * 2. Class constructor (instantiated with options and context)
184
+ * 3. Factory function (called with options and context, can be async)
185
+ *
186
+ * @example
187
+ *
188
+ * ```typescript
189
+ * // Load from file path
190
+ * const reporter = await loadReporter('./my-reporter.js', {
191
+ * verbose: true,
192
+ * });
193
+ *
194
+ * // Load from npm package
195
+ * const reporter = await loadReporter('@company/custom-reporter', {
196
+ * apiKey: 'xxx',
197
+ * });
198
+ * ```
199
+ *
200
+ * @param specifier - File path (relative or absolute) or npm package name
201
+ * @param options - Options to pass to the reporter factory/constructor
202
+ * @param cwd - Current working directory for resolving relative paths
203
+ * @returns Loaded reporter instance
204
+ * @throws ReporterLoadError if the module cannot be loaded
205
+ * @throws ReporterValidationError if the module doesn't implement Reporter
206
+ */
207
+ const loadReporter = async (specifier, options = {}, cwd = process.cwd()) => {
208
+ const context = (0, exports.createReporterContext)();
209
+ const resolvedSpecifier = resolveSpecifier(specifier, cwd);
210
+ let module;
211
+ try {
212
+ module = await Promise.resolve(`${__rewriteRelativeImportExtension(resolvedSpecifier)}`).then(s => __importStar(require(s)));
213
+ }
214
+ catch (error) {
215
+ const message = error instanceof Error ? error.message : String(error);
216
+ throw new reporter_js_1.ReporterLoadError(message, specifier, { cause: error });
217
+ }
218
+ // Handle ESM/CJS interop - get default export if present
219
+ const exported = module.default ?? module;
220
+ // Case 1: Already a Reporter object (plain object export)
221
+ if (isReporterObject(exported)) {
222
+ return exported;
223
+ }
224
+ // Case 2: Class constructor
225
+ if (isClass(exported)) {
226
+ let instance;
227
+ try {
228
+ instance = new exported(options, context);
229
+ }
230
+ catch (error) {
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ throw new reporter_js_1.ReporterLoadError(`Constructor threw error: ${message}`, specifier, { cause: error });
233
+ }
234
+ validateReporter(instance, specifier);
235
+ return instance;
236
+ }
237
+ // Case 3: Factory function (sync or async)
238
+ if (typeof exported === 'function') {
239
+ let result;
240
+ try {
241
+ result = await exported(options, context);
242
+ }
243
+ catch (error) {
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ throw new reporter_js_1.ReporterLoadError(`Factory function threw error: ${message}`, specifier, { cause: error });
246
+ }
247
+ validateReporter(result, specifier);
248
+ return result;
249
+ }
250
+ // None of the above - could be an object with missing methods or invalid type
251
+ if (typeof exported === 'object' && exported !== null) {
252
+ // It's an object but missing required methods
253
+ const missing = getMissingMethods(exported);
254
+ throw new reporter_js_1.ReporterValidationError('Module does not implement Reporter interface.', specifier, missing);
255
+ }
256
+ // Completely invalid export type
257
+ throw new reporter_js_1.ReporterValidationError('Module must export a Reporter object, class, or factory function.', specifier);
258
+ };
259
+ exports.loadReporter = loadReporter;
260
+ /**
261
+ * Resolve a specifier to an importable URL or module name
262
+ *
263
+ * @param specifier - File path or npm package name
264
+ * @param cwd - Current working directory for resolving relative paths
265
+ * @returns Resolved module specifier
266
+ */
267
+ const resolveSpecifier = (specifier, cwd) => {
268
+ if ((0, exports.isFilePath)(specifier)) {
269
+ const absolutePath = (0, node_path_1.resolve)(cwd, specifier);
270
+ return (0, node_url_1.pathToFileURL)(absolutePath).href;
271
+ }
272
+ // npm package name - return as-is for dynamic import
273
+ return specifier;
274
+ };
275
+ const validateReporter = (obj, specifier) => {
276
+ const missing = getMissingMethods(obj);
277
+ if (missing.length > 0) {
278
+ throw new reporter_js_1.ReporterValidationError('Module does not implement Reporter interface.', specifier, missing);
279
+ }
280
+ };
281
+ //# sourceMappingURL=reporter-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-loader.js","sourceRoot":"","sources":["../../src/services/reporter-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ModestBench Reporter Loader
3
+ *
4
+ * Service for loading third-party reporter plugins from file paths or npm
5
+ * packages. Supports multiple export patterns: plain objects, classes, and
6
+ * factory functions (sync or async).
7
+ */
8
+ import type { Logger, Reporter, ReporterContext } from "../types/index.cjs";
9
+ /**
10
+ * Current plugin API version
11
+ *
12
+ * Increment this when making breaking changes to the plugin API.
13
+ */
14
+ export declare const PLUGIN_API_VERSION = 1;
15
+ /**
16
+ * Create a ReporterContext for passing to plugins
17
+ *
18
+ * @param logger - Optional logger to use (defaults to console-based logger)
19
+ * @returns ReporterContext with version info and utilities
20
+ */
21
+ export declare const createReporterContext: (logger?: Logger) => ReporterContext;
22
+ /**
23
+ * Check if a specifier refers to a built-in reporter
24
+ *
25
+ * @param specifier - Reporter name or path
26
+ * @returns True if the specifier is a built-in reporter name
27
+ */
28
+ export declare const isBuiltInReporter: (specifier: string) => boolean;
29
+ /**
30
+ * Check if a specifier looks like a file path
31
+ *
32
+ * @param specifier - Reporter name or path
33
+ * @returns True if the specifier appears to be a file path
34
+ */
35
+ export declare const isFilePath: (specifier: string) => boolean;
36
+ /**
37
+ * Load a reporter from a file path or npm package name
38
+ *
39
+ * Supports multiple export patterns:
40
+ *
41
+ * 1. Plain Reporter object (simplest, no options support)
42
+ * 2. Class constructor (instantiated with options and context)
43
+ * 3. Factory function (called with options and context, can be async)
44
+ *
45
+ * @example
46
+ *
47
+ * ```typescript
48
+ * // Load from file path
49
+ * const reporter = await loadReporter('./my-reporter.js', {
50
+ * verbose: true,
51
+ * });
52
+ *
53
+ * // Load from npm package
54
+ * const reporter = await loadReporter('@company/custom-reporter', {
55
+ * apiKey: 'xxx',
56
+ * });
57
+ * ```
58
+ *
59
+ * @param specifier - File path (relative or absolute) or npm package name
60
+ * @param options - Options to pass to the reporter factory/constructor
61
+ * @param cwd - Current working directory for resolving relative paths
62
+ * @returns Loaded reporter instance
63
+ * @throws ReporterLoadError if the module cannot be loaded
64
+ * @throws ReporterValidationError if the module doesn't implement Reporter
65
+ */
66
+ export declare const loadReporter: (specifier: string, options?: Record<string, unknown>, cwd?: string) => Promise<Reporter>;
67
+ //# sourceMappingURL=reporter-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-loader.d.ts","sourceRoot":"","sources":["../../src/services/reporter-loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,2BAA0B;AAU3E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AA8BpC;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,KAAG,eAOvD,CAAC;AAkBF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,OAIrD,CAAC;AA+BF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,KAAG,OAO9C,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,MAAM,EACjB,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,MAAK,MAAsB,KAC1B,OAAO,CAAC,QAAQ,CAmFlB,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ModestBench Reporter Loader
3
+ *
4
+ * Service for loading third-party reporter plugins from file paths or npm
5
+ * packages. Supports multiple export patterns: plain objects, classes, and
6
+ * factory functions (sync or async).
7
+ */
8
+ import type { Logger, Reporter, ReporterContext } from "../types/index.js";
9
+ /**
10
+ * Current plugin API version
11
+ *
12
+ * Increment this when making breaking changes to the plugin API.
13
+ */
14
+ export declare const PLUGIN_API_VERSION = 1;
15
+ /**
16
+ * Create a ReporterContext for passing to plugins
17
+ *
18
+ * @param logger - Optional logger to use (defaults to console-based logger)
19
+ * @returns ReporterContext with version info and utilities
20
+ */
21
+ export declare const createReporterContext: (logger?: Logger) => ReporterContext;
22
+ /**
23
+ * Check if a specifier refers to a built-in reporter
24
+ *
25
+ * @param specifier - Reporter name or path
26
+ * @returns True if the specifier is a built-in reporter name
27
+ */
28
+ export declare const isBuiltInReporter: (specifier: string) => boolean;
29
+ /**
30
+ * Check if a specifier looks like a file path
31
+ *
32
+ * @param specifier - Reporter name or path
33
+ * @returns True if the specifier appears to be a file path
34
+ */
35
+ export declare const isFilePath: (specifier: string) => boolean;
36
+ /**
37
+ * Load a reporter from a file path or npm package name
38
+ *
39
+ * Supports multiple export patterns:
40
+ *
41
+ * 1. Plain Reporter object (simplest, no options support)
42
+ * 2. Class constructor (instantiated with options and context)
43
+ * 3. Factory function (called with options and context, can be async)
44
+ *
45
+ * @example
46
+ *
47
+ * ```typescript
48
+ * // Load from file path
49
+ * const reporter = await loadReporter('./my-reporter.js', {
50
+ * verbose: true,
51
+ * });
52
+ *
53
+ * // Load from npm package
54
+ * const reporter = await loadReporter('@company/custom-reporter', {
55
+ * apiKey: 'xxx',
56
+ * });
57
+ * ```
58
+ *
59
+ * @param specifier - File path (relative or absolute) or npm package name
60
+ * @param options - Options to pass to the reporter factory/constructor
61
+ * @param cwd - Current working directory for resolving relative paths
62
+ * @returns Loaded reporter instance
63
+ * @throws ReporterLoadError if the module cannot be loaded
64
+ * @throws ReporterValidationError if the module doesn't implement Reporter
65
+ */
66
+ export declare const loadReporter: (specifier: string, options?: Record<string, unknown>, cwd?: string) => Promise<Reporter>;
67
+ //# sourceMappingURL=reporter-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-loader.d.ts","sourceRoot":"","sources":["../../src/services/reporter-loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,0BAA0B;AAU3E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AA8BpC;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,KAAG,eAOvD,CAAC;AAkBF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,OAIrD,CAAC;AA+BF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,KAAG,OAO9C,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,MAAM,EACjB,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,MAAK,MAAsB,KAC1B,OAAO,CAAC,QAAQ,CAmFlB,CAAC"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * ModestBench Reporter Loader
3
+ *
4
+ * Service for loading third-party reporter plugins from file paths or npm
5
+ * packages. Supports multiple export patterns: plain objects, classes, and
6
+ * factory functions (sync or async).
7
+ */
8
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
9
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
10
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
11
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
12
+ });
13
+ }
14
+ return path;
15
+ };
16
+ import { isAbsolute, resolve } from 'node:path';
17
+ import { pathToFileURL } from 'node:url';
18
+ import { Reporters } from "../constants.js";
19
+ import { ReporterLoadError, ReporterValidationError, } from "../errors/reporter.js";
20
+ import { getPackageVersion } from "../utils/package.js";
21
+ import { reporterUtils } from "../utils/reporter-utils.js";
22
+ /**
23
+ * Current plugin API version
24
+ *
25
+ * Increment this when making breaking changes to the plugin API.
26
+ */
27
+ export const PLUGIN_API_VERSION = 1;
28
+ /**
29
+ * Set of built-in reporter names
30
+ */
31
+ const BUILT_IN_REPORTERS = new Set(Object.values(Reporters));
32
+ /**
33
+ * Required methods that all reporters must implement
34
+ */
35
+ const REQUIRED_REPORTER_METHODS = [
36
+ 'onStart',
37
+ 'onEnd',
38
+ 'onError',
39
+ 'onTaskResult',
40
+ ];
41
+ /**
42
+ * Default logger implementation using console
43
+ *
44
+ * This provides a simple console-based logger for reporter plugins.
45
+ */
46
+ const defaultLogger = {
47
+ debug: (message, ...args) => console.debug(message, ...args),
48
+ error: (message, ...args) => console.error(message, ...args),
49
+ info: (message, ...args) => console.info(message, ...args),
50
+ trace: (message, ...args) => console.trace(message, ...args),
51
+ warn: (message, ...args) => console.warn(message, ...args),
52
+ };
53
+ /**
54
+ * Create a ReporterContext for passing to plugins
55
+ *
56
+ * @param logger - Optional logger to use (defaults to console-based logger)
57
+ * @returns ReporterContext with version info and utilities
58
+ */
59
+ export const createReporterContext = (logger) => {
60
+ return {
61
+ logger: logger ?? defaultLogger,
62
+ pluginApiVersion: PLUGIN_API_VERSION,
63
+ utils: reporterUtils,
64
+ version: getPackageVersion(),
65
+ };
66
+ };
67
+ /**
68
+ * Get the list of missing required methods from a reporter object
69
+ *
70
+ * @param obj - Object to check
71
+ * @returns Array of missing method names
72
+ */
73
+ const getMissingMethods = (obj) => {
74
+ if (typeof obj !== 'object' || obj === null) {
75
+ return [...REQUIRED_REPORTER_METHODS];
76
+ }
77
+ return REQUIRED_REPORTER_METHODS.filter((method) => typeof obj[method] !== 'function');
78
+ };
79
+ /**
80
+ * Check if a specifier refers to a built-in reporter
81
+ *
82
+ * @param specifier - Reporter name or path
83
+ * @returns True if the specifier is a built-in reporter name
84
+ */
85
+ export const isBuiltInReporter = (specifier) => {
86
+ return BUILT_IN_REPORTERS.has(specifier);
87
+ };
88
+ /**
89
+ * Check if a function is a class constructor
90
+ *
91
+ * Uses heuristics to distinguish classes from regular functions:
92
+ *
93
+ * - Classes have a non-writable prototype property
94
+ * - Class syntax produces different toString() output
95
+ *
96
+ * @param func - Function to check
97
+ * @returns True if the function appears to be a class constructor
98
+ */
99
+ const isClass = (func) => {
100
+ if (typeof func !== 'function') {
101
+ return false;
102
+ }
103
+ // Classes have a non-writable prototype
104
+ const protoDescriptor = Object.getOwnPropertyDescriptor(func, 'prototype');
105
+ if (!protoDescriptor || protoDescriptor.writable) {
106
+ return false;
107
+ }
108
+ // Check if it uses class syntax (handles both 'class Foo' and 'class{')
109
+ const funcStr = func.toString();
110
+ return /^class\b/.test(funcStr);
111
+ };
112
+ /**
113
+ * Check if a specifier looks like a file path
114
+ *
115
+ * @param specifier - Reporter name or path
116
+ * @returns True if the specifier appears to be a file path
117
+ */
118
+ export const isFilePath = (specifier) => {
119
+ return (specifier.startsWith('.') ||
120
+ specifier.startsWith('/') ||
121
+ // isAbsolute handles Windows paths like 'C:\path\to\file.js'
122
+ isAbsolute(specifier));
123
+ };
124
+ /**
125
+ * Check if an object implements the Reporter interface
126
+ *
127
+ * Validates that all required methods are present and are functions.
128
+ *
129
+ * @param obj - Object to validate
130
+ * @returns True if the object has all required reporter methods
131
+ */
132
+ const isReporterObject = (obj) => {
133
+ if (typeof obj !== 'object' || obj === null) {
134
+ return false;
135
+ }
136
+ return REQUIRED_REPORTER_METHODS.every((method) => typeof obj[method] === 'function');
137
+ };
138
+ /**
139
+ * Load a reporter from a file path or npm package name
140
+ *
141
+ * Supports multiple export patterns:
142
+ *
143
+ * 1. Plain Reporter object (simplest, no options support)
144
+ * 2. Class constructor (instantiated with options and context)
145
+ * 3. Factory function (called with options and context, can be async)
146
+ *
147
+ * @example
148
+ *
149
+ * ```typescript
150
+ * // Load from file path
151
+ * const reporter = await loadReporter('./my-reporter.js', {
152
+ * verbose: true,
153
+ * });
154
+ *
155
+ * // Load from npm package
156
+ * const reporter = await loadReporter('@company/custom-reporter', {
157
+ * apiKey: 'xxx',
158
+ * });
159
+ * ```
160
+ *
161
+ * @param specifier - File path (relative or absolute) or npm package name
162
+ * @param options - Options to pass to the reporter factory/constructor
163
+ * @param cwd - Current working directory for resolving relative paths
164
+ * @returns Loaded reporter instance
165
+ * @throws ReporterLoadError if the module cannot be loaded
166
+ * @throws ReporterValidationError if the module doesn't implement Reporter
167
+ */
168
+ export const loadReporter = async (specifier, options = {}, cwd = process.cwd()) => {
169
+ const context = createReporterContext();
170
+ const resolvedSpecifier = resolveSpecifier(specifier, cwd);
171
+ let module;
172
+ try {
173
+ module = await import(__rewriteRelativeImportExtension(resolvedSpecifier));
174
+ }
175
+ catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ throw new ReporterLoadError(message, specifier, { cause: error });
178
+ }
179
+ // Handle ESM/CJS interop - get default export if present
180
+ const exported = module.default ?? module;
181
+ // Case 1: Already a Reporter object (plain object export)
182
+ if (isReporterObject(exported)) {
183
+ return exported;
184
+ }
185
+ // Case 2: Class constructor
186
+ if (isClass(exported)) {
187
+ let instance;
188
+ try {
189
+ instance = new exported(options, context);
190
+ }
191
+ catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ throw new ReporterLoadError(`Constructor threw error: ${message}`, specifier, { cause: error });
194
+ }
195
+ validateReporter(instance, specifier);
196
+ return instance;
197
+ }
198
+ // Case 3: Factory function (sync or async)
199
+ if (typeof exported === 'function') {
200
+ let result;
201
+ try {
202
+ result = await exported(options, context);
203
+ }
204
+ catch (error) {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ throw new ReporterLoadError(`Factory function threw error: ${message}`, specifier, { cause: error });
207
+ }
208
+ validateReporter(result, specifier);
209
+ return result;
210
+ }
211
+ // None of the above - could be an object with missing methods or invalid type
212
+ if (typeof exported === 'object' && exported !== null) {
213
+ // It's an object but missing required methods
214
+ const missing = getMissingMethods(exported);
215
+ throw new ReporterValidationError('Module does not implement Reporter interface.', specifier, missing);
216
+ }
217
+ // Completely invalid export type
218
+ throw new ReporterValidationError('Module must export a Reporter object, class, or factory function.', specifier);
219
+ };
220
+ /**
221
+ * Resolve a specifier to an importable URL or module name
222
+ *
223
+ * @param specifier - File path or npm package name
224
+ * @param cwd - Current working directory for resolving relative paths
225
+ * @returns Resolved module specifier
226
+ */
227
+ const resolveSpecifier = (specifier, cwd) => {
228
+ if (isFilePath(specifier)) {
229
+ const absolutePath = resolve(cwd, specifier);
230
+ return pathToFileURL(absolutePath).href;
231
+ }
232
+ // npm package name - return as-is for dynamic import
233
+ return specifier;
234
+ };
235
+ const validateReporter = (obj, specifier) => {
236
+ const missing = getMissingMethods(obj);
237
+ if (missing.length > 0) {
238
+ throw new ReporterValidationError('Module does not implement Reporter interface.', specifier, missing);
239
+ }
240
+ };
241
+ //# sourceMappingURL=reporter-loader.js.map