@testsmith/perfornium 0.1.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 (164) hide show
  1. package/README.md +360 -0
  2. package/dist/cli/cli.d.ts +2 -0
  3. package/dist/cli/cli.js +192 -0
  4. package/dist/cli/commands/distributed.d.ts +11 -0
  5. package/dist/cli/commands/distributed.js +179 -0
  6. package/dist/cli/commands/import.d.ts +23 -0
  7. package/dist/cli/commands/import.js +461 -0
  8. package/dist/cli/commands/init.d.ts +7 -0
  9. package/dist/cli/commands/init.js +923 -0
  10. package/dist/cli/commands/mock.d.ts +7 -0
  11. package/dist/cli/commands/mock.js +281 -0
  12. package/dist/cli/commands/report.d.ts +5 -0
  13. package/dist/cli/commands/report.js +70 -0
  14. package/dist/cli/commands/run.d.ts +12 -0
  15. package/dist/cli/commands/run.js +260 -0
  16. package/dist/cli/commands/validate.d.ts +3 -0
  17. package/dist/cli/commands/validate.js +35 -0
  18. package/dist/cli/commands/worker.d.ts +27 -0
  19. package/dist/cli/commands/worker.js +320 -0
  20. package/dist/config/index.d.ts +2 -0
  21. package/dist/config/index.js +20 -0
  22. package/dist/config/parser.d.ts +19 -0
  23. package/dist/config/parser.js +330 -0
  24. package/dist/config/types/global-config.d.ts +74 -0
  25. package/dist/config/types/global-config.js +2 -0
  26. package/dist/config/types/hooks.d.ts +58 -0
  27. package/dist/config/types/hooks.js +3 -0
  28. package/dist/config/types/import-types.d.ts +33 -0
  29. package/dist/config/types/import-types.js +2 -0
  30. package/dist/config/types/index.d.ts +11 -0
  31. package/dist/config/types/index.js +27 -0
  32. package/dist/config/types/load-config.d.ts +32 -0
  33. package/dist/config/types/load-config.js +9 -0
  34. package/dist/config/types/output-config.d.ts +10 -0
  35. package/dist/config/types/output-config.js +2 -0
  36. package/dist/config/types/report-config.d.ts +10 -0
  37. package/dist/config/types/report-config.js +2 -0
  38. package/dist/config/types/runtime-types.d.ts +6 -0
  39. package/dist/config/types/runtime-types.js +2 -0
  40. package/dist/config/types/scenario-config.d.ts +30 -0
  41. package/dist/config/types/scenario-config.js +2 -0
  42. package/dist/config/types/step-types.d.ts +139 -0
  43. package/dist/config/types/step-types.js +2 -0
  44. package/dist/config/types/test-configuration.d.ts +18 -0
  45. package/dist/config/types/test-configuration.js +2 -0
  46. package/dist/config/types/worker-config.d.ts +12 -0
  47. package/dist/config/types/worker-config.js +2 -0
  48. package/dist/config/validator.d.ts +19 -0
  49. package/dist/config/validator.js +198 -0
  50. package/dist/core/csv-data-provider.d.ts +47 -0
  51. package/dist/core/csv-data-provider.js +265 -0
  52. package/dist/core/hooks-manager.d.ts +33 -0
  53. package/dist/core/hooks-manager.js +129 -0
  54. package/dist/core/index.d.ts +5 -0
  55. package/dist/core/index.js +11 -0
  56. package/dist/core/script-executor.d.ts +14 -0
  57. package/dist/core/script-executor.js +290 -0
  58. package/dist/core/step-executor.d.ts +41 -0
  59. package/dist/core/step-executor.js +680 -0
  60. package/dist/core/test-runner.d.ts +34 -0
  61. package/dist/core/test-runner.js +465 -0
  62. package/dist/core/threshold-evaluator.d.ts +43 -0
  63. package/dist/core/threshold-evaluator.js +170 -0
  64. package/dist/core/virtual-user-pool.d.ts +42 -0
  65. package/dist/core/virtual-user-pool.js +136 -0
  66. package/dist/core/virtual-user.d.ts +51 -0
  67. package/dist/core/virtual-user.js +488 -0
  68. package/dist/distributed/coordinator.d.ts +34 -0
  69. package/dist/distributed/coordinator.js +158 -0
  70. package/dist/distributed/health-monitor.d.ts +18 -0
  71. package/dist/distributed/health-monitor.js +72 -0
  72. package/dist/distributed/load-distributor.d.ts +17 -0
  73. package/dist/distributed/load-distributor.js +106 -0
  74. package/dist/distributed/remote-worker.d.ts +37 -0
  75. package/dist/distributed/remote-worker.js +241 -0
  76. package/dist/distributed/result-aggregator.d.ts +43 -0
  77. package/dist/distributed/result-aggregator.js +146 -0
  78. package/dist/dsl/index.d.ts +3 -0
  79. package/dist/dsl/index.js +11 -0
  80. package/dist/dsl/test-builder.d.ts +111 -0
  81. package/dist/dsl/test-builder.js +514 -0
  82. package/dist/importers/har-importer.d.ts +17 -0
  83. package/dist/importers/har-importer.js +172 -0
  84. package/dist/importers/open-api-importer.d.ts +23 -0
  85. package/dist/importers/open-api-importer.js +181 -0
  86. package/dist/importers/wsdl-importer.d.ts +42 -0
  87. package/dist/importers/wsdl-importer.js +440 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +17 -0
  90. package/dist/load-patterns/arrivals.d.ts +7 -0
  91. package/dist/load-patterns/arrivals.js +118 -0
  92. package/dist/load-patterns/base.d.ts +9 -0
  93. package/dist/load-patterns/base.js +2 -0
  94. package/dist/load-patterns/basic.d.ts +7 -0
  95. package/dist/load-patterns/basic.js +117 -0
  96. package/dist/load-patterns/stepping.d.ts +6 -0
  97. package/dist/load-patterns/stepping.js +122 -0
  98. package/dist/metrics/collector.d.ts +72 -0
  99. package/dist/metrics/collector.js +662 -0
  100. package/dist/metrics/types.d.ts +135 -0
  101. package/dist/metrics/types.js +2 -0
  102. package/dist/outputs/base.d.ts +7 -0
  103. package/dist/outputs/base.js +2 -0
  104. package/dist/outputs/csv.d.ts +13 -0
  105. package/dist/outputs/csv.js +163 -0
  106. package/dist/outputs/graphite.d.ts +13 -0
  107. package/dist/outputs/graphite.js +126 -0
  108. package/dist/outputs/influxdb.d.ts +12 -0
  109. package/dist/outputs/influxdb.js +82 -0
  110. package/dist/outputs/json.d.ts +14 -0
  111. package/dist/outputs/json.js +107 -0
  112. package/dist/outputs/streaming-csv.d.ts +37 -0
  113. package/dist/outputs/streaming-csv.js +254 -0
  114. package/dist/outputs/streaming-json.d.ts +43 -0
  115. package/dist/outputs/streaming-json.js +353 -0
  116. package/dist/outputs/webhook.d.ts +16 -0
  117. package/dist/outputs/webhook.js +96 -0
  118. package/dist/protocols/base.d.ts +33 -0
  119. package/dist/protocols/base.js +2 -0
  120. package/dist/protocols/rest/handler.d.ts +67 -0
  121. package/dist/protocols/rest/handler.js +776 -0
  122. package/dist/protocols/soap/handler.d.ts +12 -0
  123. package/dist/protocols/soap/handler.js +165 -0
  124. package/dist/protocols/web/core-web-vitals.d.ts +121 -0
  125. package/dist/protocols/web/core-web-vitals.js +373 -0
  126. package/dist/protocols/web/handler.d.ts +50 -0
  127. package/dist/protocols/web/handler.js +706 -0
  128. package/dist/recorder/native-recorder.d.ts +14 -0
  129. package/dist/recorder/native-recorder.js +533 -0
  130. package/dist/recorder/scenario-recorder.d.ts +55 -0
  131. package/dist/recorder/scenario-recorder.js +296 -0
  132. package/dist/reporting/constants.d.ts +94 -0
  133. package/dist/reporting/constants.js +82 -0
  134. package/dist/reporting/enhanced-html-generator.d.ts +55 -0
  135. package/dist/reporting/enhanced-html-generator.js +965 -0
  136. package/dist/reporting/generator.d.ts +42 -0
  137. package/dist/reporting/generator.js +1217 -0
  138. package/dist/reporting/statistics.d.ts +144 -0
  139. package/dist/reporting/statistics.js +742 -0
  140. package/dist/reporting/templates/enhanced-report.hbs +2812 -0
  141. package/dist/reporting/templates/html.hbs +2453 -0
  142. package/dist/utils/faker-manager.d.ts +55 -0
  143. package/dist/utils/faker-manager.js +166 -0
  144. package/dist/utils/file-manager.d.ts +33 -0
  145. package/dist/utils/file-manager.js +154 -0
  146. package/dist/utils/handlebars-manager.d.ts +42 -0
  147. package/dist/utils/handlebars-manager.js +172 -0
  148. package/dist/utils/logger.d.ts +16 -0
  149. package/dist/utils/logger.js +46 -0
  150. package/dist/utils/template.d.ts +80 -0
  151. package/dist/utils/template.js +513 -0
  152. package/dist/utils/test-output-writer.d.ts +56 -0
  153. package/dist/utils/test-output-writer.js +643 -0
  154. package/dist/utils/time.d.ts +3 -0
  155. package/dist/utils/time.js +23 -0
  156. package/dist/utils/timestamp-helper.d.ts +17 -0
  157. package/dist/utils/timestamp-helper.js +53 -0
  158. package/dist/workers/manager.d.ts +18 -0
  159. package/dist/workers/manager.js +95 -0
  160. package/dist/workers/server.d.ts +21 -0
  161. package/dist/workers/server.js +205 -0
  162. package/dist/workers/worker.d.ts +19 -0
  163. package/dist/workers/worker.js +147 -0
  164. package/package.json +102 -0
@@ -0,0 +1,7 @@
1
+ interface MockOptions {
2
+ port?: string;
3
+ host?: string;
4
+ delay?: string;
5
+ }
6
+ export declare function mockCommand(options: MockOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.mockCommand = mockCommand;
37
+ const http = __importStar(require("http"));
38
+ const logger_1 = require("../../utils/logger");
39
+ // Sample data for mock endpoints
40
+ const users = [
41
+ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'admin' },
42
+ { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'user' },
43
+ { id: 3, name: 'Charlie Brown', email: 'charlie@example.com', role: 'user' },
44
+ { id: 4, name: 'Diana Prince', email: 'diana@example.com', role: 'moderator' },
45
+ { id: 5, name: 'Eve Wilson', email: 'eve@example.com', role: 'user' },
46
+ ];
47
+ const products = [
48
+ { id: 'P001', name: 'Laptop', price: 999.99, category: 'Electronics', inStock: true },
49
+ { id: 'P002', name: 'Smartphone', price: 599.99, category: 'Electronics', inStock: true },
50
+ { id: 'P003', name: 'Headphones', price: 149.99, category: 'Electronics', inStock: false },
51
+ { id: 'P004', name: 'Keyboard', price: 79.99, category: 'Accessories', inStock: true },
52
+ { id: 'P005', name: 'Mouse', price: 39.99, category: 'Accessories', inStock: true },
53
+ ];
54
+ let requestCount = 0;
55
+ const startTime = Date.now();
56
+ async function mockCommand(options) {
57
+ const port = parseInt(options.port || '3000');
58
+ const host = options.host || 'localhost';
59
+ const delay = parseInt(options.delay || '0');
60
+ const server = http.createServer(async (req, res) => {
61
+ requestCount++;
62
+ const requestId = requestCount;
63
+ // Add artificial delay if configured
64
+ if (delay > 0) {
65
+ await new Promise(resolve => setTimeout(resolve, delay));
66
+ }
67
+ // CORS headers
68
+ res.setHeader('Access-Control-Allow-Origin', '*');
69
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
70
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
71
+ res.setHeader('Content-Type', 'application/json');
72
+ // Handle OPTIONS (preflight)
73
+ if (req.method === 'OPTIONS') {
74
+ res.writeHead(204);
75
+ res.end();
76
+ return;
77
+ }
78
+ const url = new URL(req.url || '/', `http://${host}:${port}`);
79
+ const path = url.pathname;
80
+ logger_1.logger.debug(`[${requestId}] ${req.method} ${path}`);
81
+ try {
82
+ // Route handling
83
+ if (path === '/status' || path === '/health') {
84
+ // Health check endpoint
85
+ res.writeHead(200);
86
+ res.end(JSON.stringify({
87
+ status: 'ok',
88
+ timestamp: new Date().toISOString(),
89
+ uptime: Math.floor((Date.now() - startTime) / 1000),
90
+ requests: requestCount
91
+ }));
92
+ }
93
+ else if (path === '/users' && req.method === 'GET') {
94
+ // List users
95
+ res.writeHead(200);
96
+ res.end(JSON.stringify(users));
97
+ }
98
+ else if (path.match(/^\/users\/\d+$/) && req.method === 'GET') {
99
+ // Get single user
100
+ const id = parseInt(path.split('/')[2]);
101
+ const user = users.find(u => u.id === id);
102
+ if (user) {
103
+ res.writeHead(200);
104
+ res.end(JSON.stringify(user));
105
+ }
106
+ else {
107
+ res.writeHead(404);
108
+ res.end(JSON.stringify({ error: 'User not found', id }));
109
+ }
110
+ }
111
+ else if (path === '/users' && req.method === 'POST') {
112
+ // Create user
113
+ let body = '';
114
+ req.on('data', chunk => body += chunk);
115
+ req.on('end', () => {
116
+ try {
117
+ const data = JSON.parse(body);
118
+ const newUser = {
119
+ id: users.length + 1,
120
+ name: data.name || 'New User',
121
+ email: data.email || 'new@example.com',
122
+ role: data.role || 'user'
123
+ };
124
+ users.push(newUser);
125
+ res.writeHead(201);
126
+ res.end(JSON.stringify(newUser));
127
+ }
128
+ catch {
129
+ res.writeHead(400);
130
+ res.end(JSON.stringify({ error: 'Invalid JSON body' }));
131
+ }
132
+ });
133
+ return;
134
+ }
135
+ else if (path === '/products' && req.method === 'GET') {
136
+ // List products with optional filtering
137
+ const category = url.searchParams.get('category');
138
+ const inStock = url.searchParams.get('inStock');
139
+ let filtered = [...products];
140
+ if (category) {
141
+ filtered = filtered.filter(p => p.category.toLowerCase() === category.toLowerCase());
142
+ }
143
+ if (inStock !== null) {
144
+ filtered = filtered.filter(p => p.inStock === (inStock === 'true'));
145
+ }
146
+ res.writeHead(200);
147
+ res.end(JSON.stringify(filtered));
148
+ }
149
+ else if (path.match(/^\/products\/P\d+$/) && req.method === 'GET') {
150
+ // Get single product
151
+ const id = path.split('/')[2];
152
+ const product = products.find(p => p.id === id);
153
+ if (product) {
154
+ res.writeHead(200);
155
+ res.end(JSON.stringify(product));
156
+ }
157
+ else {
158
+ res.writeHead(404);
159
+ res.end(JSON.stringify({ error: 'Product not found', id }));
160
+ }
161
+ }
162
+ else if (path === '/auth/login' && req.method === 'POST') {
163
+ // Login endpoint
164
+ let body = '';
165
+ req.on('data', chunk => body += chunk);
166
+ req.on('end', () => {
167
+ try {
168
+ const data = JSON.parse(body);
169
+ if (data.username && data.password) {
170
+ res.writeHead(200);
171
+ res.end(JSON.stringify({
172
+ token: `mock-token-${Date.now()}`,
173
+ user: { username: data.username, role: 'user' },
174
+ expiresIn: 3600
175
+ }));
176
+ }
177
+ else {
178
+ res.writeHead(401);
179
+ res.end(JSON.stringify({ error: 'Invalid credentials' }));
180
+ }
181
+ }
182
+ catch {
183
+ res.writeHead(400);
184
+ res.end(JSON.stringify({ error: 'Invalid JSON body' }));
185
+ }
186
+ });
187
+ return;
188
+ }
189
+ else if (path === '/echo' && req.method === 'POST') {
190
+ // Echo back the request body
191
+ let body = '';
192
+ req.on('data', chunk => body += chunk);
193
+ req.on('end', () => {
194
+ res.writeHead(200);
195
+ res.end(JSON.stringify({
196
+ method: req.method,
197
+ path: path,
198
+ headers: req.headers,
199
+ body: body ? JSON.parse(body) : null,
200
+ timestamp: new Date().toISOString()
201
+ }));
202
+ });
203
+ return;
204
+ }
205
+ else if (path === '/delay' && req.method === 'GET') {
206
+ // Configurable delay endpoint
207
+ const ms = parseInt(url.searchParams.get('ms') || '1000');
208
+ await new Promise(resolve => setTimeout(resolve, ms));
209
+ res.writeHead(200);
210
+ res.end(JSON.stringify({ delayed: ms, timestamp: new Date().toISOString() }));
211
+ }
212
+ else if (path === '/error' && req.method === 'GET') {
213
+ // Simulate error responses
214
+ const code = parseInt(url.searchParams.get('code') || '500');
215
+ res.writeHead(code);
216
+ res.end(JSON.stringify({ error: `Simulated ${code} error`, code }));
217
+ }
218
+ else if (path === '/random' && req.method === 'GET') {
219
+ // Random data endpoint
220
+ res.writeHead(200);
221
+ res.end(JSON.stringify({
222
+ id: Math.floor(Math.random() * 10000),
223
+ uuid: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
224
+ timestamp: new Date().toISOString(),
225
+ random: Math.random()
226
+ }));
227
+ }
228
+ else {
229
+ // 404 for unknown routes
230
+ res.writeHead(404);
231
+ res.end(JSON.stringify({
232
+ error: 'Not found',
233
+ path,
234
+ availableEndpoints: [
235
+ 'GET /status',
236
+ 'GET /health',
237
+ 'GET /users',
238
+ 'GET /users/:id',
239
+ 'POST /users',
240
+ 'GET /products',
241
+ 'GET /products/:id',
242
+ 'POST /auth/login',
243
+ 'POST /echo',
244
+ 'GET /delay?ms=1000',
245
+ 'GET /error?code=500',
246
+ 'GET /random'
247
+ ]
248
+ }));
249
+ }
250
+ }
251
+ catch {
252
+ res.writeHead(500);
253
+ res.end(JSON.stringify({ error: 'Internal server error' }));
254
+ }
255
+ });
256
+ server.listen(port, host, () => {
257
+ logger_1.logger.success(`\n🚀 Mock server running at http://${host}:${port}\n`);
258
+ logger_1.logger.info('Available endpoints:');
259
+ logger_1.logger.info(' GET /status - Health check');
260
+ logger_1.logger.info(' GET /health - Health check (alias)');
261
+ logger_1.logger.info(' GET /users - List all users');
262
+ logger_1.logger.info(' GET /users/:id - Get user by ID');
263
+ logger_1.logger.info(' POST /users - Create user');
264
+ logger_1.logger.info(' GET /products - List products (?category=X&inStock=true)');
265
+ logger_1.logger.info(' GET /products/:id - Get product by ID');
266
+ logger_1.logger.info(' POST /auth/login - Login (returns token)');
267
+ logger_1.logger.info(' POST /echo - Echo request body');
268
+ logger_1.logger.info(' GET /delay?ms=1000 - Delayed response');
269
+ logger_1.logger.info(' GET /error?code=500 - Simulate error');
270
+ logger_1.logger.info(' GET /random - Random data');
271
+ logger_1.logger.info('\nPress Ctrl+C to stop\n');
272
+ });
273
+ // Handle graceful shutdown
274
+ process.on('SIGINT', () => {
275
+ logger_1.logger.info('\nShutting down mock server...');
276
+ server.close(() => {
277
+ logger_1.logger.success('Mock server stopped');
278
+ process.exit(0);
279
+ });
280
+ });
281
+ }
@@ -0,0 +1,5 @@
1
+ export declare function reportCommand(resultsPath: string, options: {
2
+ output?: string;
3
+ template?: string;
4
+ title?: string;
5
+ }): Promise<void>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.reportCommand = reportCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const enhanced_html_generator_1 = require("../../reporting/enhanced-html-generator");
40
+ const logger_1 = require("../../utils/logger");
41
+ async function reportCommand(resultsPath, options) {
42
+ try {
43
+ logger_1.logger.info(`📊 Generating report from: ${resultsPath}`);
44
+ if (!fs.existsSync(resultsPath)) {
45
+ logger_1.logger.error(`❌ Results file not found: ${resultsPath}`);
46
+ process.exit(1);
47
+ }
48
+ const resultsContent = fs.readFileSync(resultsPath, 'utf8');
49
+ let resultsData;
50
+ if (resultsPath.endsWith('.json')) {
51
+ resultsData = JSON.parse(resultsContent);
52
+ }
53
+ else {
54
+ logger_1.logger.error('❌ Only JSON results files are currently supported');
55
+ process.exit(1);
56
+ }
57
+ const generator = new enhanced_html_generator_1.EnhancedHTMLReportGenerator();
58
+ const outputPath = options.output || 'report.html';
59
+ await generator.generate({
60
+ testName: options.title || resultsData.testName || 'Performance Test',
61
+ summary: resultsData.summary,
62
+ results: resultsData.results || []
63
+ }, outputPath);
64
+ logger_1.logger.success(`✅ Report generated: ${path.resolve(outputPath)}`);
65
+ }
66
+ catch (error) {
67
+ logger_1.logger.error(`❌ Report generation failed: ${error.message}`);
68
+ process.exit(1);
69
+ }
70
+ }
@@ -0,0 +1,12 @@
1
+ export interface RunOptions {
2
+ env?: string;
3
+ workers?: string;
4
+ output?: string;
5
+ report?: boolean;
6
+ dryRun?: boolean;
7
+ verbose?: boolean;
8
+ debug?: boolean;
9
+ maxUsers?: string;
10
+ global?: string[];
11
+ }
12
+ export declare function runCommand(configPath: string, options: RunOptions): Promise<void>;
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCommand = runCommand;
37
+ const path = __importStar(require("path"));
38
+ const http = __importStar(require("http"));
39
+ const parser_1 = require("../../config/parser");
40
+ const validator_1 = require("../../config/validator");
41
+ const test_runner_1 = require("../../core/test-runner");
42
+ const logger_1 = require("../../utils/logger");
43
+ // Lightweight mock server for testing
44
+ let mockServer = null;
45
+ let mockRequestCount = 0;
46
+ const mockStartTime = Date.now();
47
+ function startMockServer(port = 3000) {
48
+ return new Promise((resolve, reject) => {
49
+ mockServer = http.createServer((req, res) => {
50
+ mockRequestCount++;
51
+ res.setHeader('Content-Type', 'application/json');
52
+ res.setHeader('Access-Control-Allow-Origin', '*');
53
+ if (req.method === 'OPTIONS') {
54
+ res.writeHead(204);
55
+ res.end();
56
+ return;
57
+ }
58
+ // Simple /status endpoint
59
+ res.writeHead(200);
60
+ res.end(JSON.stringify({
61
+ status: 'ok',
62
+ timestamp: new Date().toISOString(),
63
+ uptime: Math.floor((Date.now() - mockStartTime) / 1000),
64
+ requests: mockRequestCount
65
+ }));
66
+ });
67
+ mockServer.on('error', (err) => {
68
+ if (err.code === 'EADDRINUSE') {
69
+ logger_1.logger.warn(`Port ${port} in use, mock server not started`);
70
+ resolve(); // Continue without mock server
71
+ }
72
+ else {
73
+ reject(err);
74
+ }
75
+ });
76
+ mockServer.listen(port, 'localhost', () => {
77
+ logger_1.logger.info(`🎯 Mock server started at http://localhost:${port}/status`);
78
+ resolve();
79
+ });
80
+ });
81
+ }
82
+ function stopMockServer() {
83
+ if (mockServer) {
84
+ mockServer.close();
85
+ mockServer = null;
86
+ }
87
+ }
88
+ async function runCommand(configPath, options) {
89
+ try {
90
+ // Set log level: default=WARN, --verbose=INFO, --debug=DEBUG
91
+ if (options.debug) {
92
+ logger_1.logger.setLevel(logger_1.LogLevel.DEBUG);
93
+ }
94
+ else if (options.verbose) {
95
+ logger_1.logger.setLevel(logger_1.LogLevel.INFO);
96
+ }
97
+ logger_1.logger.info(`Loading configuration: ${configPath}`);
98
+ const parser = new parser_1.ConfigParser();
99
+ const config = await parser.parse(configPath, options.env);
100
+ // Apply CLI global variable overrides
101
+ applyGlobalOverrides(config, options);
102
+ // Auto-start mock server if base_url targets localhost:3000
103
+ const baseUrl = config.global?.base_url || '';
104
+ if (baseUrl.match(/^https?:\/\/localhost:3000/)) {
105
+ await startMockServer(3000);
106
+ }
107
+ // Apply max users override
108
+ if (options.maxUsers) {
109
+ const maxUsers = parseInt(options.maxUsers);
110
+ const { getPrimaryLoadPhase } = await Promise.resolve().then(() => __importStar(require('../../config/types/load-config')));
111
+ const primaryPhase = getPrimaryLoadPhase(config.load);
112
+ const currentUsers = primaryPhase.virtual_users || primaryPhase.vus;
113
+ if (currentUsers && currentUsers > maxUsers) {
114
+ logger_1.logger.warn(`Limiting virtual users from ${currentUsers} to ${maxUsers}`);
115
+ primaryPhase.virtual_users = maxUsers;
116
+ }
117
+ }
118
+ // Process templates with environment variables
119
+ const processedConfig = parser.processTemplates(config, {
120
+ env: process.env,
121
+ timestamp: Date.now(),
122
+ datetime: new Date().toISOString()
123
+ });
124
+ logger_1.logger.info(`Validating configuration...`);
125
+ const validator = new validator_1.ConfigValidator();
126
+ const validation = validator.validate(processedConfig);
127
+ if (!validation.valid) {
128
+ logger_1.logger.error('Configuration validation failed:');
129
+ validation.errors.forEach(error => logger_1.logger.error(` - ${error}`));
130
+ process.exit(1);
131
+ }
132
+ if (validation.warnings.length > 0) {
133
+ logger_1.logger.warn('Configuration warnings:');
134
+ validation.warnings.forEach(warning => logger_1.logger.warn(` - ${warning}`));
135
+ }
136
+ if (options.dryRun) {
137
+ logger_1.logger.success('Configuration is valid');
138
+ return;
139
+ }
140
+ // Override output directory if specified
141
+ if (options.output && processedConfig.outputs) {
142
+ processedConfig.outputs.forEach(output => {
143
+ if (output.file) {
144
+ output.file = path.join(options.output, path.basename(output.file));
145
+ }
146
+ });
147
+ }
148
+ // Enable report generation if requested
149
+ if (options.report) {
150
+ processedConfig.report = {
151
+ generate: true,
152
+ output: options.output ? path.join(options.output, 'report.html') : 'report.html',
153
+ ...processedConfig.report
154
+ };
155
+ }
156
+ if (options.workers) {
157
+ const { WorkerManager } = await Promise.resolve().then(() => __importStar(require('../../workers/manager')));
158
+ const manager = new WorkerManager();
159
+ const workerAddresses = options.workers.split(',').map(w => w.trim());
160
+ for (const address of workerAddresses) {
161
+ await manager.addWorker(address);
162
+ }
163
+ await manager.distributeTest(processedConfig);
164
+ await manager.waitForCompletion();
165
+ const metrics = manager.getAggregatedMetrics();
166
+ const summary = metrics.getSummary();
167
+ logger_1.logger.success(`Distributed test completed: ${summary.success_rate.toFixed(2)}% success rate`);
168
+ await manager.cleanup();
169
+ }
170
+ else {
171
+ const runner = new test_runner_1.TestRunner(processedConfig);
172
+ // Set up graceful shutdown
173
+ process.on('SIGINT', async () => {
174
+ logger_1.logger.info('Received SIGINT, stopping test...');
175
+ await runner.stop();
176
+ process.exit(0);
177
+ });
178
+ await runner.run();
179
+ }
180
+ }
181
+ catch (error) {
182
+ logger_1.logger.error(`Test execution failed: ${error.message}`);
183
+ if (options.verbose || options.debug) {
184
+ console.error(error.stack);
185
+ }
186
+ process.exit(1);
187
+ }
188
+ finally {
189
+ // Stop mock server if it was started
190
+ stopMockServer();
191
+ }
192
+ }
193
+ /**
194
+ * Apply CLI global variable overrides to the config
195
+ * Supports dot notation for nested properties: browser.headless=false
196
+ */
197
+ function applyGlobalOverrides(config, options) {
198
+ if (!options.global || options.global.length === 0) {
199
+ return;
200
+ }
201
+ // Ensure global config exists
202
+ if (!config.global) {
203
+ config.global = {};
204
+ }
205
+ for (const override of options.global) {
206
+ const eqIndex = override.indexOf('=');
207
+ if (eqIndex === -1) {
208
+ logger_1.logger.warn(`Invalid global override format: ${override} (expected key=value)`);
209
+ continue;
210
+ }
211
+ const key = override.substring(0, eqIndex);
212
+ const rawValue = override.substring(eqIndex + 1);
213
+ const value = parseValue(rawValue);
214
+ logger_1.logger.info(`Overriding global.${key} = ${JSON.stringify(value)}`);
215
+ setNestedValue(config.global, key, value);
216
+ }
217
+ }
218
+ /**
219
+ * Parse a string value to its appropriate type
220
+ */
221
+ function parseValue(value) {
222
+ // Boolean
223
+ if (value.toLowerCase() === 'true')
224
+ return true;
225
+ if (value.toLowerCase() === 'false')
226
+ return false;
227
+ // Number (integer or float)
228
+ if (/^-?\d+$/.test(value))
229
+ return parseInt(value, 10);
230
+ if (/^-?\d+\.\d+$/.test(value))
231
+ return parseFloat(value);
232
+ // JSON object or array
233
+ if ((value.startsWith('{') && value.endsWith('}')) ||
234
+ (value.startsWith('[') && value.endsWith(']'))) {
235
+ try {
236
+ return JSON.parse(value);
237
+ }
238
+ catch {
239
+ // Not valid JSON, return as string
240
+ }
241
+ }
242
+ // String (keep as-is)
243
+ return value;
244
+ }
245
+ /**
246
+ * Set a nested value using dot notation
247
+ * Example: setNestedValue(obj, 'browser.headless', false)
248
+ */
249
+ function setNestedValue(obj, path, value) {
250
+ const parts = path.split('.');
251
+ let current = obj;
252
+ for (let i = 0; i < parts.length - 1; i++) {
253
+ const part = parts[i];
254
+ if (!(part in current) || typeof current[part] !== 'object') {
255
+ current[part] = {};
256
+ }
257
+ current = current[part];
258
+ }
259
+ current[parts[parts.length - 1]] = value;
260
+ }
@@ -0,0 +1,3 @@
1
+ export declare function validateCommand(configPath: string, options: {
2
+ env?: string;
3
+ }): Promise<void>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateCommand = validateCommand;
4
+ const parser_1 = require("../../config/parser");
5
+ const validator_1 = require("../../config/validator");
6
+ const logger_1 = require("../../utils/logger");
7
+ async function validateCommand(configPath, options) {
8
+ try {
9
+ logger_1.logger.info(`🔍 Validating configuration: ${configPath}`);
10
+ const parser = new parser_1.ConfigParser();
11
+ const config = await parser.parse(configPath, options.env);
12
+ const validator = new validator_1.ConfigValidator();
13
+ const result = validator.validate(config);
14
+ if (result.valid) {
15
+ logger_1.logger.success('✅ Configuration is valid');
16
+ if (result.warnings.length > 0) {
17
+ logger_1.logger.warn('⚠️ Warnings:');
18
+ result.warnings.forEach(warning => logger_1.logger.warn(` - ${warning}`));
19
+ }
20
+ }
21
+ else {
22
+ logger_1.logger.error('❌ Configuration validation failed:');
23
+ result.errors.forEach(error => logger_1.logger.error(` - ${error}`));
24
+ if (result.warnings.length > 0) {
25
+ logger_1.logger.warn('⚠️ Warnings:');
26
+ result.warnings.forEach(warning => logger_1.logger.warn(` - ${warning}`));
27
+ }
28
+ process.exit(1);
29
+ }
30
+ }
31
+ catch (error) {
32
+ logger_1.logger.error(`❌ Validation failed: ${error.message}`);
33
+ process.exit(1);
34
+ }
35
+ }