@apiquest/fracture 1.0.2 → 1.0.4

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 (168) hide show
  1. package/README.md +119 -0
  2. package/bin/cli.js +2 -2
  3. package/dist/CollectionRunner.js +3 -3
  4. package/dist/ScriptEngine.js +4 -4
  5. package/dist/cli/plugin-commands.d.ts.map +1 -1
  6. package/dist/cli/plugin-commands.js +2 -1
  7. package/dist/cli/plugin-commands.js.map +1 -1
  8. package/package.json +55 -50
  9. package/src/CollectionAnalyzer.ts +102 -102
  10. package/src/CollectionRunner.ts +1423 -1423
  11. package/src/CollectionRunner.types.ts +9 -9
  12. package/src/CollectionValidator.ts +289 -289
  13. package/src/ConsoleReporter.ts +143 -143
  14. package/src/CookieJar.ts +258 -258
  15. package/src/DagScheduler.ts +439 -439
  16. package/src/Logger.ts +85 -85
  17. package/src/PluginLoader.ts +126 -126
  18. package/src/PluginManager.ts +208 -208
  19. package/src/PluginResolver.ts +154 -154
  20. package/src/QuestAPI.ts +764 -764
  21. package/src/QuestAPI.types.ts +33 -33
  22. package/src/QuestTestAPI.ts +164 -164
  23. package/src/RequestFilter.ts +224 -224
  24. package/src/ScriptEngine.ts +219 -219
  25. package/src/ScriptValidator.ts +428 -428
  26. package/src/TaskGraph.ts +598 -598
  27. package/src/TestCounter.ts +109 -109
  28. package/src/VariableResolver.ts +114 -114
  29. package/src/cli/index.ts +480 -480
  30. package/src/cli/plugin-commands.ts +342 -341
  31. package/src/cli/plugin-discovery.ts +44 -44
  32. package/src/index.ts +24 -24
  33. package/src/utils.ts +52 -52
  34. package/tsconfig.json +20 -20
  35. package/tsconfig.test.json +5 -5
  36. package/vitest.config.ts +22 -22
  37. package/dist/ExecutionTree.d.ts +0 -77
  38. package/dist/ExecutionTree.d.ts.map +0 -1
  39. package/dist/ExecutionTree.js +0 -265
  40. package/dist/ExecutionTree.js.map +0 -1
  41. package/dist/fracture/src/CollectionAnalyzer.d.ts +0 -17
  42. package/dist/fracture/src/CollectionAnalyzer.d.ts.map +0 -1
  43. package/dist/fracture/src/CollectionAnalyzer.js +0 -70
  44. package/dist/fracture/src/CollectionAnalyzer.js.map +0 -1
  45. package/dist/fracture/src/CollectionRunner.d.ts +0 -39
  46. package/dist/fracture/src/CollectionRunner.d.ts.map +0 -1
  47. package/dist/fracture/src/CollectionRunner.js +0 -802
  48. package/dist/fracture/src/CollectionRunner.js.map +0 -1
  49. package/dist/fracture/src/CollectionRunner.types.d.ts +0 -8
  50. package/dist/fracture/src/CollectionRunner.types.d.ts.map +0 -1
  51. package/dist/fracture/src/CollectionRunner.types.js +0 -2
  52. package/dist/fracture/src/CollectionRunner.types.js.map +0 -1
  53. package/dist/fracture/src/CollectionValidator.d.ts +0 -14
  54. package/dist/fracture/src/CollectionValidator.d.ts.map +0 -1
  55. package/dist/fracture/src/CollectionValidator.js +0 -145
  56. package/dist/fracture/src/CollectionValidator.js.map +0 -1
  57. package/dist/fracture/src/ConsoleReporter.d.ts +0 -24
  58. package/dist/fracture/src/ConsoleReporter.d.ts.map +0 -1
  59. package/dist/fracture/src/ConsoleReporter.js +0 -123
  60. package/dist/fracture/src/ConsoleReporter.js.map +0 -1
  61. package/dist/fracture/src/CookieJar.d.ts +0 -70
  62. package/dist/fracture/src/CookieJar.d.ts.map +0 -1
  63. package/dist/fracture/src/CookieJar.js +0 -233
  64. package/dist/fracture/src/CookieJar.js.map +0 -1
  65. package/dist/fracture/src/ExecutionTree.d.ts +0 -77
  66. package/dist/fracture/src/ExecutionTree.d.ts.map +0 -1
  67. package/dist/fracture/src/ExecutionTree.js +0 -258
  68. package/dist/fracture/src/ExecutionTree.js.map +0 -1
  69. package/dist/fracture/src/Logger.d.ts +0 -25
  70. package/dist/fracture/src/Logger.d.ts.map +0 -1
  71. package/dist/fracture/src/Logger.js +0 -78
  72. package/dist/fracture/src/Logger.js.map +0 -1
  73. package/dist/fracture/src/PluginLoader.d.ts +0 -23
  74. package/dist/fracture/src/PluginLoader.d.ts.map +0 -1
  75. package/dist/fracture/src/PluginLoader.js +0 -102
  76. package/dist/fracture/src/PluginLoader.js.map +0 -1
  77. package/dist/fracture/src/PluginManager.d.ts +0 -64
  78. package/dist/fracture/src/PluginManager.d.ts.map +0 -1
  79. package/dist/fracture/src/PluginManager.js +0 -162
  80. package/dist/fracture/src/PluginManager.js.map +0 -1
  81. package/dist/fracture/src/PluginResolver.d.ts +0 -35
  82. package/dist/fracture/src/PluginResolver.d.ts.map +0 -1
  83. package/dist/fracture/src/PluginResolver.js +0 -128
  84. package/dist/fracture/src/PluginResolver.js.map +0 -1
  85. package/dist/fracture/src/QuestAPI.d.ts +0 -9
  86. package/dist/fracture/src/QuestAPI.d.ts.map +0 -1
  87. package/dist/fracture/src/QuestAPI.js +0 -679
  88. package/dist/fracture/src/QuestAPI.js.map +0 -1
  89. package/dist/fracture/src/QuestAPI.types.d.ts +0 -35
  90. package/dist/fracture/src/QuestAPI.types.d.ts.map +0 -1
  91. package/dist/fracture/src/QuestAPI.types.js +0 -3
  92. package/dist/fracture/src/QuestAPI.types.js.map +0 -1
  93. package/dist/fracture/src/QuestTestAPI.d.ts +0 -12
  94. package/dist/fracture/src/QuestTestAPI.d.ts.map +0 -1
  95. package/dist/fracture/src/QuestTestAPI.js +0 -133
  96. package/dist/fracture/src/QuestTestAPI.js.map +0 -1
  97. package/dist/fracture/src/ScriptEngine.d.ts +0 -21
  98. package/dist/fracture/src/ScriptEngine.d.ts.map +0 -1
  99. package/dist/fracture/src/ScriptEngine.js +0 -183
  100. package/dist/fracture/src/ScriptEngine.js.map +0 -1
  101. package/dist/fracture/src/ScriptValidator.d.ts +0 -68
  102. package/dist/fracture/src/ScriptValidator.d.ts.map +0 -1
  103. package/dist/fracture/src/ScriptValidator.js +0 -351
  104. package/dist/fracture/src/ScriptValidator.js.map +0 -1
  105. package/dist/fracture/src/TestCounter.d.ts +0 -18
  106. package/dist/fracture/src/TestCounter.d.ts.map +0 -1
  107. package/dist/fracture/src/TestCounter.js +0 -82
  108. package/dist/fracture/src/TestCounter.js.map +0 -1
  109. package/dist/fracture/src/VariableResolver.d.ts +0 -20
  110. package/dist/fracture/src/VariableResolver.d.ts.map +0 -1
  111. package/dist/fracture/src/VariableResolver.js +0 -100
  112. package/dist/fracture/src/VariableResolver.js.map +0 -1
  113. package/dist/fracture/src/cli/index.d.ts +0 -3
  114. package/dist/fracture/src/cli/index.d.ts.map +0 -1
  115. package/dist/fracture/src/cli/index.js +0 -347
  116. package/dist/fracture/src/cli/index.js.map +0 -1
  117. package/dist/fracture/src/cli/plugin-commands.d.ts +0 -6
  118. package/dist/fracture/src/cli/plugin-commands.d.ts.map +0 -1
  119. package/dist/fracture/src/cli/plugin-commands.js +0 -263
  120. package/dist/fracture/src/cli/plugin-commands.js.map +0 -1
  121. package/dist/fracture/src/cli/plugin-discovery.d.ts +0 -11
  122. package/dist/fracture/src/cli/plugin-discovery.d.ts.map +0 -1
  123. package/dist/fracture/src/cli/plugin-discovery.js +0 -64
  124. package/dist/fracture/src/cli/plugin-discovery.js.map +0 -1
  125. package/dist/fracture/src/index.d.ts +0 -13
  126. package/dist/fracture/src/index.d.ts.map +0 -1
  127. package/dist/fracture/src/index.js +0 -17
  128. package/dist/fracture/src/index.js.map +0 -1
  129. package/dist/fracture/src/utils.d.ts +0 -28
  130. package/dist/fracture/src/utils.d.ts.map +0 -1
  131. package/dist/fracture/src/utils.js +0 -48
  132. package/dist/fracture/src/utils.js.map +0 -1
  133. package/dist/plugin-auth/src/apikey-auth.d.ts +0 -3
  134. package/dist/plugin-auth/src/apikey-auth.d.ts.map +0 -1
  135. package/dist/plugin-auth/src/apikey-auth.js +0 -73
  136. package/dist/plugin-auth/src/apikey-auth.js.map +0 -1
  137. package/dist/plugin-auth/src/basic-auth.d.ts +0 -3
  138. package/dist/plugin-auth/src/basic-auth.d.ts.map +0 -1
  139. package/dist/plugin-auth/src/basic-auth.js +0 -61
  140. package/dist/plugin-auth/src/basic-auth.js.map +0 -1
  141. package/dist/plugin-auth/src/bearer-auth.d.ts +0 -3
  142. package/dist/plugin-auth/src/bearer-auth.d.ts.map +0 -1
  143. package/dist/plugin-auth/src/bearer-auth.js +0 -49
  144. package/dist/plugin-auth/src/bearer-auth.js.map +0 -1
  145. package/dist/plugin-auth/src/helpers.d.ts +0 -3
  146. package/dist/plugin-auth/src/helpers.d.ts.map +0 -1
  147. package/dist/plugin-auth/src/helpers.js +0 -8
  148. package/dist/plugin-auth/src/helpers.js.map +0 -1
  149. package/dist/plugin-auth/src/index.d.ts +0 -10
  150. package/dist/plugin-auth/src/index.d.ts.map +0 -1
  151. package/dist/plugin-auth/src/index.js +0 -25
  152. package/dist/plugin-auth/src/index.js.map +0 -1
  153. package/dist/plugin-auth/src/oauth2-auth.d.ts +0 -35
  154. package/dist/plugin-auth/src/oauth2-auth.d.ts.map +0 -1
  155. package/dist/plugin-auth/src/oauth2-auth.js +0 -266
  156. package/dist/plugin-auth/src/oauth2-auth.js.map +0 -1
  157. package/dist/plugin-http/src/index.d.ts +0 -4
  158. package/dist/plugin-http/src/index.d.ts.map +0 -1
  159. package/dist/plugin-http/src/index.js +0 -266
  160. package/dist/plugin-http/src/index.js.map +0 -1
  161. package/dist/plugin-vault-file/src/index.d.ts +0 -67
  162. package/dist/plugin-vault-file/src/index.d.ts.map +0 -1
  163. package/dist/plugin-vault-file/src/index.js +0 -171
  164. package/dist/plugin-vault-file/src/index.js.map +0 -1
  165. package/dist/types.d.ts +0 -374
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -13
  168. package/dist/types.js.map +0 -1
@@ -1,219 +1,219 @@
1
- import vm from 'vm';
2
- import { expect } from 'chai';
3
- import _ from 'lodash';
4
- import type { ExecutionContext, ScriptResult, TestResult } from '@apiquest/types';
5
- import { ScriptType } from '@apiquest/types';
6
- import { Logger } from './Logger.js';
7
- import { createQuestAPI } from './QuestAPI.js';
8
- import { isNullOrWhitespace } from './utils.js';
9
-
10
- interface ConsoleAPI {
11
- log(...args: unknown[]): void;
12
- info(...args: unknown[]): void;
13
- warn(...args: unknown[]): void;
14
- error(...args: unknown[]): void;
15
- }
16
-
17
- export class ScriptEngine {
18
- private tests: TestResult[] = [];
19
- private consoleOutput: string[] = [];
20
- private logger: Logger;
21
-
22
- constructor(baseLogger?: Logger) {
23
- this.logger = baseLogger?.createLogger('ScriptEngine') ?? new Logger('ScriptEngine');
24
- }
25
-
26
- /**
27
- * Execute a script in VM context with quest API
28
- */
29
- async execute(
30
- script: string,
31
- context: ExecutionContext,
32
- scriptType: ScriptType,
33
- emitAssertion: (test: TestResult) => void
34
- ): Promise<ScriptResult> {
35
- if (isNullOrWhitespace(script)) {
36
- this.logger.trace('Empty script, skipping execution');
37
- return {
38
- success: true,
39
- tests: [],
40
- consoleOutput: []
41
- };
42
- }
43
-
44
- const scriptPreview = script.length > 100 ? script.substring(0, 100) + '...' : script;
45
- this.logger.debug(`Executing ${scriptType} script (${script.length} chars)`);
46
- this.logger.trace(`Script preview: ${scriptPreview}`);
47
-
48
- this.tests = [];
49
- this.consoleOutput = [];
50
-
51
- try {
52
- this.logger.trace('Creating quest API sandbox');
53
- const questAPI = createQuestAPI(context, scriptType, this.tests, emitAssertion);
54
-
55
- const sandbox = {
56
- quest: questAPI,
57
- expect,
58
- _,
59
- console: this.createConsoleAPI(),
60
- require: this.createRequire(),
61
- setTimeout,
62
- setInterval,
63
- clearTimeout,
64
- clearInterval,
65
- Promise,
66
- Buffer,
67
- AbortController,
68
- AbortSignal,
69
- signal: context.abortSignal
70
- };
71
-
72
- const vmContext = vm.createContext(sandbox);
73
- this.logger.trace('VM context created');
74
-
75
- // Wrap script in async function to support top-level await
76
- const wrappedScript = `
77
- (async () => {
78
- ${script}
79
- })()
80
- `;
81
-
82
- // Execute script and get the promise
83
- const startTime = Date.now();
84
- const result = vm.runInContext(wrappedScript, vmContext, {
85
- timeout: 30000,
86
- displayErrors: true
87
- }) as unknown;
88
-
89
- // If result is a promise, wait for it
90
- if (result !== null && result !== undefined && typeof (result as { then?: unknown }).then === 'function') {
91
- await (result as Promise<void>);
92
- }
93
-
94
- // Wait for any other pending promises
95
- await new Promise(resolve => setImmediate(resolve));
96
-
97
- const duration = Date.now() - startTime;
98
- this.logger.debug(`Script executed successfully in ${duration}ms`);
99
- this.logger.trace(`Tests collected: ${this.tests.length}, Console output: ${this.consoleOutput.length} lines`);
100
-
101
- return {
102
- success: true,
103
- tests: this.tests,
104
- consoleOutput: this.consoleOutput
105
- };
106
- } catch (error: unknown) {
107
- const errorMsg = (error as { message?: string }).message ?? String(error);
108
- const errorStack = (error as { stack?: string }).stack;
109
- const errorName = (error as { name?: string }).name;
110
-
111
- // Check if this was an abort
112
- if (errorName === 'AbortError' || errorMsg.includes('abort') || errorMsg.includes('Abort')) {
113
- this.logger.debug('Script execution interrupted by abort signal');
114
- return {
115
- success: false,
116
- tests: this.tests,
117
- error: 'Script aborted',
118
- consoleOutput: this.consoleOutput
119
- };
120
- }
121
-
122
- this.logger.error(`Script execution failed: ${errorMsg}`);
123
- if (errorStack !== undefined) {
124
- this.logger.trace(`Error stack: ${errorStack}`);
125
- }
126
- return {
127
- success: false,
128
- tests: this.tests,
129
- error: errorMsg,
130
- consoleOutput: this.consoleOutput
131
- };
132
- }
133
- }
134
-
135
- /**
136
- * Create console API that captures output
137
- */
138
- private createConsoleAPI(): ConsoleAPI {
139
- const self = this;
140
-
141
- const safeStringify = (value: unknown): string => {
142
- if (typeof value === 'string') return value;
143
- if (value === null || value === undefined) return String(value);
144
- if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') return String(value);
145
- if (typeof value === 'symbol') return value.toString();
146
- if (typeof value === 'function') return '[Function]';
147
-
148
- try {
149
- const seen = new WeakSet<object>();
150
- return JSON.stringify(
151
- value,
152
- (_key, val: unknown) => {
153
- if (typeof val === 'bigint') {
154
- return val.toString();
155
- }
156
- if (typeof val === 'function') {
157
- return '[Function]';
158
- }
159
- if (typeof val === 'symbol') {
160
- return val.toString();
161
- }
162
- if (typeof val === 'object' && val !== null) {
163
- if (seen.has(val)) {
164
- return '[Circular]';
165
- }
166
- seen.add(val);
167
- }
168
- return val as string | number | boolean | null;
169
- },
170
- 2
171
- );
172
- } catch {
173
- return String(value);
174
- }
175
- };
176
-
177
- return {
178
- log(...args: unknown[]) {
179
- const message = args.map(safeStringify).join(' ');
180
- self.consoleOutput.push(message);
181
- console.log(message);
182
- },
183
- info(...args: unknown[]) {
184
- const message = args.map(safeStringify).join(' ');
185
- self.consoleOutput.push(`[INFO] ${message}`);
186
- console.info(message);
187
- },
188
- warn(...args: unknown[]) {
189
- const message = args.map(safeStringify).join(' ');
190
- self.consoleOutput.push(`[WARN] ${message}`);
191
- console.warn(message);
192
- },
193
- error(...args: unknown[]) {
194
- const message = args.map(safeStringify).join(' ');
195
- self.consoleOutput.push(`[ERROR] ${message}`);
196
- console.error(message);
197
- }
198
- };
199
- }
200
-
201
- /**
202
- * Create minimal require function
203
- */
204
- private createRequire() {
205
- return (moduleName: string) => {
206
- const allowedModules: Record<string, unknown> = {
207
- 'chai': require('chai') as unknown,
208
- 'lodash': require('lodash') as unknown,
209
- 'moment': require('moment') as unknown
210
- };
211
-
212
- if (moduleName in allowedModules) {
213
- return allowedModules[moduleName];
214
- }
215
-
216
- throw new Error(`Module '${moduleName}' is not allowed in scripts`);
217
- };
218
- }
219
- }
1
+ import vm from 'vm';
2
+ import { expect } from 'chai';
3
+ import _ from 'lodash';
4
+ import type { ExecutionContext, ScriptResult, TestResult } from '@apiquest/types';
5
+ import { ScriptType } from '@apiquest/types';
6
+ import { Logger } from './Logger.js';
7
+ import { createQuestAPI } from './QuestAPI.js';
8
+ import { isNullOrWhitespace } from './utils.js';
9
+
10
+ interface ConsoleAPI {
11
+ log(...args: unknown[]): void;
12
+ info(...args: unknown[]): void;
13
+ warn(...args: unknown[]): void;
14
+ error(...args: unknown[]): void;
15
+ }
16
+
17
+ export class ScriptEngine {
18
+ private tests: TestResult[] = [];
19
+ private consoleOutput: string[] = [];
20
+ private logger: Logger;
21
+
22
+ constructor(baseLogger?: Logger) {
23
+ this.logger = baseLogger?.createLogger('ScriptEngine') ?? new Logger('ScriptEngine');
24
+ }
25
+
26
+ /**
27
+ * Execute a script in VM context with quest API
28
+ */
29
+ async execute(
30
+ script: string,
31
+ context: ExecutionContext,
32
+ scriptType: ScriptType,
33
+ emitAssertion: (test: TestResult) => void
34
+ ): Promise<ScriptResult> {
35
+ if (isNullOrWhitespace(script)) {
36
+ this.logger.trace('Empty script, skipping execution');
37
+ return {
38
+ success: true,
39
+ tests: [],
40
+ consoleOutput: []
41
+ };
42
+ }
43
+
44
+ const scriptPreview = script.length > 100 ? script.substring(0, 100) + '...' : script;
45
+ this.logger.debug(`Executing ${scriptType} script (${script.length} chars)`);
46
+ this.logger.trace(`Script preview: ${scriptPreview}`);
47
+
48
+ this.tests = [];
49
+ this.consoleOutput = [];
50
+
51
+ try {
52
+ this.logger.trace('Creating quest API sandbox');
53
+ const questAPI = createQuestAPI(context, scriptType, this.tests, emitAssertion);
54
+
55
+ const sandbox = {
56
+ quest: questAPI,
57
+ expect,
58
+ _,
59
+ console: this.createConsoleAPI(),
60
+ require: this.createRequire(),
61
+ setTimeout,
62
+ setInterval,
63
+ clearTimeout,
64
+ clearInterval,
65
+ Promise,
66
+ Buffer,
67
+ AbortController,
68
+ AbortSignal,
69
+ signal: context.abortSignal
70
+ };
71
+
72
+ const vmContext = vm.createContext(sandbox);
73
+ this.logger.trace('VM context created');
74
+
75
+ // Wrap script in async function to support top-level await
76
+ const wrappedScript = `
77
+ (async () => {
78
+ ${script}
79
+ })()
80
+ `;
81
+
82
+ // Execute script and get the promise
83
+ const startTime = Date.now();
84
+ const result = vm.runInContext(wrappedScript, vmContext, {
85
+ timeout: 30000,
86
+ displayErrors: true
87
+ }) as unknown;
88
+
89
+ // If result is a promise, wait for it
90
+ if (result !== null && result !== undefined && typeof (result as { then?: unknown }).then === 'function') {
91
+ await (result as Promise<void>);
92
+ }
93
+
94
+ // Wait for any other pending promises
95
+ await new Promise(resolve => setImmediate(resolve));
96
+
97
+ const duration = Date.now() - startTime;
98
+ this.logger.debug(`Script executed successfully in ${duration}ms`);
99
+ this.logger.trace(`Tests collected: ${this.tests.length}, Console output: ${this.consoleOutput.length} lines`);
100
+
101
+ return {
102
+ success: true,
103
+ tests: this.tests,
104
+ consoleOutput: this.consoleOutput
105
+ };
106
+ } catch (error: unknown) {
107
+ const errorMsg = (error as { message?: string }).message ?? String(error);
108
+ const errorStack = (error as { stack?: string }).stack;
109
+ const errorName = (error as { name?: string }).name;
110
+
111
+ // Check if this was an abort
112
+ if (errorName === 'AbortError' || errorMsg.includes('abort') || errorMsg.includes('Abort')) {
113
+ this.logger.debug('Script execution interrupted by abort signal');
114
+ return {
115
+ success: false,
116
+ tests: this.tests,
117
+ error: 'Script aborted',
118
+ consoleOutput: this.consoleOutput
119
+ };
120
+ }
121
+
122
+ this.logger.error(`Script execution failed: ${errorMsg}`);
123
+ if (errorStack !== undefined) {
124
+ this.logger.trace(`Error stack: ${errorStack}`);
125
+ }
126
+ return {
127
+ success: false,
128
+ tests: this.tests,
129
+ error: errorMsg,
130
+ consoleOutput: this.consoleOutput
131
+ };
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Create console API that captures output
137
+ */
138
+ private createConsoleAPI(): ConsoleAPI {
139
+ const self = this;
140
+
141
+ const safeStringify = (value: unknown): string => {
142
+ if (typeof value === 'string') return value;
143
+ if (value === null || value === undefined) return String(value);
144
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') return String(value);
145
+ if (typeof value === 'symbol') return value.toString();
146
+ if (typeof value === 'function') return '[Function]';
147
+
148
+ try {
149
+ const seen = new WeakSet<object>();
150
+ return JSON.stringify(
151
+ value,
152
+ (_key, val: unknown) => {
153
+ if (typeof val === 'bigint') {
154
+ return val.toString();
155
+ }
156
+ if (typeof val === 'function') {
157
+ return '[Function]';
158
+ }
159
+ if (typeof val === 'symbol') {
160
+ return val.toString();
161
+ }
162
+ if (typeof val === 'object' && val !== null) {
163
+ if (seen.has(val)) {
164
+ return '[Circular]';
165
+ }
166
+ seen.add(val);
167
+ }
168
+ return val as string | number | boolean | null;
169
+ },
170
+ 2
171
+ );
172
+ } catch {
173
+ return String(value);
174
+ }
175
+ };
176
+
177
+ return {
178
+ log(...args: unknown[]) {
179
+ const message = args.map(safeStringify).join(' ');
180
+ self.consoleOutput.push(message);
181
+ console.log(message);
182
+ },
183
+ info(...args: unknown[]) {
184
+ const message = args.map(safeStringify).join(' ');
185
+ self.consoleOutput.push(`[INFO] ${message}`);
186
+ console.info(message);
187
+ },
188
+ warn(...args: unknown[]) {
189
+ const message = args.map(safeStringify).join(' ');
190
+ self.consoleOutput.push(`[WARN] ${message}`);
191
+ console.warn(message);
192
+ },
193
+ error(...args: unknown[]) {
194
+ const message = args.map(safeStringify).join(' ');
195
+ self.consoleOutput.push(`[ERROR] ${message}`);
196
+ console.error(message);
197
+ }
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Create minimal require function
203
+ */
204
+ private createRequire() {
205
+ return (moduleName: string) => {
206
+ const allowedModules: Record<string, unknown> = {
207
+ 'chai': require('chai') as unknown,
208
+ 'lodash': require('lodash') as unknown,
209
+ 'moment': require('moment') as unknown
210
+ };
211
+
212
+ if (moduleName in allowedModules) {
213
+ return allowedModules[moduleName];
214
+ }
215
+
216
+ throw new Error(`Module '${moduleName}' is not allowed in scripts`);
217
+ };
218
+ }
219
+ }