@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,179 @@
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.distributedCommand = distributedCommand;
37
+ const coordinator_1 = require("../../distributed/coordinator");
38
+ const config_1 = require("../../config");
39
+ const validator_1 = require("../../config/validator");
40
+ const logger_1 = require("../../utils/logger");
41
+ const timestamp_helper_1 = require("../../utils/timestamp-helper");
42
+ const fs = __importStar(require("fs"));
43
+ async function distributedCommand(configPath, options) {
44
+ try {
45
+ // Set log level: default=WARN, --verbose=INFO, --debug=DEBUG
46
+ if (options.debug) {
47
+ logger_1.logger.setLevel(logger_1.LogLevel.DEBUG);
48
+ }
49
+ else if (options.verbose) {
50
+ logger_1.logger.setLevel(logger_1.LogLevel.INFO);
51
+ }
52
+ // Parse test configuration
53
+ const parser = new config_1.ConfigParser();
54
+ const testConfig = await parser.parse(configPath, options.env);
55
+ // Validate configuration
56
+ const validator = new validator_1.ConfigValidator();
57
+ const validation = validator.validate(testConfig);
58
+ if (!validation.valid) {
59
+ logger_1.logger.error('❌ Configuration validation failed:');
60
+ validation.errors.forEach(error => logger_1.logger.error(` - ${error}`));
61
+ process.exit(1);
62
+ }
63
+ // Parse worker configurations
64
+ const workers = parseWorkerConfigs(options.workers, options.workersFile);
65
+ if (workers.length === 0) {
66
+ logger_1.logger.error('❌ No workers specified');
67
+ process.exit(1);
68
+ }
69
+ // Create distributed test configuration
70
+ const distributedConfig = {
71
+ workers,
72
+ strategy: options.strategy || 'capacity_based',
73
+ sync_start: options.syncStart || false,
74
+ heartbeat_interval: 30000,
75
+ timeout: 300000,
76
+ retry_failed: true
77
+ };
78
+ // Initialize and run distributed test
79
+ const coordinator = new coordinator_1.DistributedCoordinator(distributedConfig);
80
+ // Set up graceful shutdown
81
+ process.on('SIGINT', async () => {
82
+ logger_1.logger.info('🛑 Received SIGINT, stopping distributed test...');
83
+ await coordinator.stop();
84
+ await coordinator.cleanup();
85
+ process.exit(0);
86
+ });
87
+ await coordinator.initialize();
88
+ logger_1.logger.info(`🚀 Starting distributed test with ${workers.length} workers`);
89
+ logger_1.logger.info(`📊 Strategy: ${distributedConfig.strategy}`);
90
+ logger_1.logger.info(`🔄 Sync start: ${distributedConfig.sync_start ? 'Yes' : 'No'}`);
91
+ await coordinator.executeTest(testConfig);
92
+ // Get aggregated results
93
+ const results = coordinator.getAggregatedResults();
94
+ logger_1.logger.info('📊 Distributed Test Results:');
95
+ logger_1.logger.info(` Total Requests: ${results.summary.total_requests}`);
96
+ logger_1.logger.info(` Success Rate: ${results.summary.success_rate.toFixed(2)}%`);
97
+ logger_1.logger.info(` Avg Response Time: ${results.summary.avg_response_time.toFixed(2)}ms`);
98
+ logger_1.logger.info(` Requests/sec: ${results.summary.requests_per_second.toFixed(2)}`);
99
+ // Output results if specified
100
+ if (options.output) {
101
+ const outputPath = options.output.endsWith('.json') ? options.output : `${options.output}/distributed-results.json`;
102
+ fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
103
+ logger_1.logger.success(`📄 Results written to: ${outputPath}`);
104
+ }
105
+ // Generate report if requested
106
+ if (options.report || testConfig.report?.generate) {
107
+ try {
108
+ const { EnhancedHTMLReportGenerator } = await Promise.resolve().then(() => __importStar(require('../../reporting/enhanced-html-generator')));
109
+ const generator = new EnhancedHTMLReportGenerator();
110
+ const timestamp = timestamp_helper_1.TimestampHelper.getTimestamp('file');
111
+ const reportFilename = `distributed-report-${timestamp}.html`;
112
+ // Always use timestamped filename for distributed reports in reports folder
113
+ const reportPath = options.output ?
114
+ `${options.output}/${reportFilename}` :
115
+ `reports/${reportFilename}`;
116
+ await generator.generate({
117
+ testName: `${testConfig.name} (Distributed)`,
118
+ summary: results.summary,
119
+ results: results.results
120
+ }, reportPath);
121
+ logger_1.logger.success(`📋 Report generated: ${reportPath}`);
122
+ }
123
+ catch (error) {
124
+ logger_1.logger.error(`❌ Report generation failed: ${error}`);
125
+ }
126
+ }
127
+ await coordinator.cleanup();
128
+ }
129
+ catch (error) {
130
+ logger_1.logger.error(`❌ Distributed test failed: ${error.message}`);
131
+ if (options.verbose || options.debug) {
132
+ console.error(error.stack);
133
+ }
134
+ process.exit(1);
135
+ }
136
+ }
137
+ function parseWorkerConfigs(workersString, workersFile) {
138
+ const workers = [];
139
+ // Parse from command line
140
+ if (workersString) {
141
+ const workerAddresses = workersString.split(',').map(w => w.trim());
142
+ workerAddresses.forEach(address => {
143
+ const [host, portStr] = address.split(':');
144
+ const port = parseInt(portStr || '8080');
145
+ workers.push({
146
+ host: host.trim(),
147
+ port,
148
+ capacity: 100, // Default capacity
149
+ region: 'default'
150
+ });
151
+ });
152
+ }
153
+ // Parse from file
154
+ if (workersFile) {
155
+ try {
156
+ const fileContent = fs.readFileSync(workersFile, 'utf8');
157
+ const fileWorkers = JSON.parse(fileContent);
158
+ if (Array.isArray(fileWorkers)) {
159
+ // Apply defaults to workers from file
160
+ fileWorkers.forEach((w) => {
161
+ workers.push({
162
+ host: w.host || 'localhost',
163
+ port: w.port || 8080,
164
+ capacity: w.capacity ?? 100, // Default capacity if not specified
165
+ region: w.region || 'default'
166
+ });
167
+ });
168
+ }
169
+ else {
170
+ throw new Error('Workers file must contain an array of worker configurations');
171
+ }
172
+ }
173
+ catch (error) {
174
+ logger_1.logger.error(`❌ Failed to parse workers file ${workersFile}:`, error);
175
+ throw error;
176
+ }
177
+ }
178
+ return workers;
179
+ }
@@ -0,0 +1,23 @@
1
+ interface ImportOptions {
2
+ output: string;
3
+ format: 'yaml' | 'typescript';
4
+ verbose?: boolean;
5
+ interactive?: boolean;
6
+ autoCorrelate?: boolean;
7
+ baseUrl?: string;
8
+ sourceFile?: string;
9
+ tags?: string;
10
+ excludeTags?: string;
11
+ paths?: string;
12
+ methods?: string;
13
+ filterDomains?: string;
14
+ excludeDomains?: string;
15
+ services?: string;
16
+ operations?: string;
17
+ folders?: string;
18
+ scenariosPerFile?: string;
19
+ confidence?: string;
20
+ [key: string]: any;
21
+ }
22
+ export declare function importCommand(type: 'openapi' | 'wsdl' | 'har' | 'postman', sourceFile: string, options: ImportOptions): Promise<void>;
23
+ export {};
@@ -0,0 +1,461 @@
1
+ "use strict";
2
+ // src/commands/import.ts
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.importCommand = importCommand;
41
+ const fs = __importStar(require("fs/promises"));
42
+ const path = __importStar(require("path"));
43
+ const yaml = __importStar(require("js-yaml"));
44
+ const inquirer_1 = __importDefault(require("inquirer"));
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const ora_1 = __importDefault(require("ora"));
47
+ const open_api_importer_1 = require("../../importers/open-api-importer");
48
+ const wsdl_importer_1 = require("../../importers/wsdl-importer");
49
+ const har_importer_1 = require("../../importers/har-importer");
50
+ const test_output_writer_1 = require("../../utils/test-output-writer");
51
+ async function importCommand(type, sourceFile, options) {
52
+ const spinner = (0, ora_1.default)(`Loading ${type.toUpperCase()} from ${sourceFile}`).start();
53
+ try {
54
+ // Validate source file exists
55
+ await fs.access(sourceFile);
56
+ let importer;
57
+ let endpoints = [];
58
+ // Create appropriate importer
59
+ switch (type) {
60
+ case 'openapi':
61
+ importer = await createOpenAPIImporter(sourceFile, options);
62
+ endpoints = importer.extractEndpoints();
63
+ break;
64
+ case 'wsdl':
65
+ importer = await createWSDLImporter(sourceFile, options);
66
+ endpoints = importer.extractServices();
67
+ break;
68
+ case 'har':
69
+ importer = await createHARImporter(sourceFile, options);
70
+ endpoints = importer.extractEndpoints();
71
+ break;
72
+ // case 'postman':
73
+ // importer = await createPostmanImporter(sourceFile, options);
74
+ // endpoints = importer.extractRequests();
75
+ // break;
76
+ default:
77
+ throw new Error(`Unsupported import type: ${type}`);
78
+ }
79
+ spinner.succeed(`Found ${endpoints.length} ${type === 'wsdl' ? 'services' : 'endpoints'}`);
80
+ if (endpoints.length === 0) {
81
+ console.log(chalk_1.default.yellow('No endpoints found to import'));
82
+ return;
83
+ }
84
+ // Apply filters
85
+ endpoints = applyFilters(endpoints, type, options);
86
+ if (endpoints.length === 0) {
87
+ console.log(chalk_1.default.yellow('No endpoints match the specified filters'));
88
+ return;
89
+ }
90
+ console.log(chalk_1.default.blue(`${endpoints.length} endpoints after filtering`));
91
+ // Interactive selection if enabled
92
+ let selectedEndpoints = endpoints;
93
+ if (options.interactive) {
94
+ selectedEndpoints = await interactiveEndpointSelection(endpoints, type);
95
+ }
96
+ if (selectedEndpoints.length === 0) {
97
+ console.log(chalk_1.default.yellow('No endpoints selected'));
98
+ return;
99
+ }
100
+ // Generate scenarios
101
+ console.log(chalk_1.default.blue(`Generating scenarios for ${selectedEndpoints.length} endpoints...`));
102
+ let scenarios = importer.generateScenarios(selectedEndpoints);
103
+ // Apply correlations if enabled
104
+ if (options.autoCorrelate) {
105
+ console.log(chalk_1.default.blue('Analyzing data correlations...'));
106
+ scenarios = await applyCorrelations(scenarios, options);
107
+ }
108
+ // Generate output files
109
+ await generateOutputFiles(scenarios, type, options);
110
+ console.log(chalk_1.default.green(`Successfully imported ${scenarios.length} scenarios to ${options.output}/`));
111
+ }
112
+ catch (error) {
113
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
114
+ spinner.fail(`Import failed: ${errorMessage}`);
115
+ if (options.verbose && error instanceof Error) {
116
+ console.error(error.stack);
117
+ }
118
+ process.exit(1);
119
+ }
120
+ }
121
+ async function createOpenAPIImporter(sourceFile, options) {
122
+ const content = await fs.readFile(sourceFile, 'utf8');
123
+ let spec;
124
+ if (sourceFile.endsWith('.json')) {
125
+ spec = JSON.parse(content);
126
+ }
127
+ else {
128
+ spec = yaml.load(content);
129
+ }
130
+ // Apply base URL override
131
+ if (options.baseUrl && spec.servers) {
132
+ spec.servers[0].url = options.baseUrl;
133
+ }
134
+ return new open_api_importer_1.OpenAPIImporter(spec);
135
+ }
136
+ async function createWSDLImporter(sourceFile, _options) {
137
+ const content = await fs.readFile(sourceFile, 'utf8');
138
+ return new wsdl_importer_1.WSDLImporter(content);
139
+ }
140
+ async function createHARImporter(sourceFile, _options) {
141
+ const content = await fs.readFile(sourceFile, 'utf8');
142
+ const har = JSON.parse(content);
143
+ return new har_importer_1.HARImporter(har);
144
+ }
145
+ // async function createPostmanImporter(sourceFile: string, options: ImportOptions): Promise<PostmanImporter> {
146
+ // const content = await fs.readFile(sourceFile, 'utf8');
147
+ // const collection = JSON.parse(content);
148
+ //
149
+ // let environment = {};
150
+ // if (options.environment) {
151
+ // const envContent = await fs.readFile(options.environment, 'utf8');
152
+ // environment = JSON.parse(envContent);
153
+ // }
154
+ //
155
+ // return new PostmanImporter(collection, environment);
156
+ // }
157
+ function applyFilters(endpoints, type, options) {
158
+ let filtered = [...endpoints];
159
+ // Apply type-specific filters
160
+ switch (type) {
161
+ case 'openapi':
162
+ filtered = applyOpenAPIFilters(filtered, options);
163
+ break;
164
+ case 'har':
165
+ filtered = applyHARFilters(filtered, options);
166
+ break;
167
+ case 'wsdl':
168
+ filtered = applyWSDLFilters(filtered, options);
169
+ break;
170
+ case 'postman':
171
+ filtered = applyPostmanFilters(filtered, options);
172
+ break;
173
+ }
174
+ return filtered;
175
+ }
176
+ function applyOpenAPIFilters(endpoints, options) {
177
+ let filtered = endpoints;
178
+ // Filter by tags
179
+ if (options.tags) {
180
+ const includeTags = options.tags.split(',').map((t) => t.trim());
181
+ filtered = filtered.filter(ep => ep.tags && ep.tags.some((tag) => includeTags.includes(tag)));
182
+ }
183
+ if (options.excludeTags) {
184
+ const excludeTags = options.excludeTags.split(',').map((t) => t.trim());
185
+ filtered = filtered.filter(ep => !ep.tags || !ep.tags.some((tag) => excludeTags.includes(tag)));
186
+ }
187
+ // Filter by paths
188
+ if (options.paths) {
189
+ const pathPatterns = options.paths.split(',').map((p) => new RegExp(p.trim()));
190
+ filtered = filtered.filter(ep => pathPatterns.some((pattern) => pattern.test(ep.path)));
191
+ }
192
+ // Filter by methods
193
+ if (options.methods) {
194
+ const methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
195
+ filtered = filtered.filter(ep => methods.includes(ep.method));
196
+ }
197
+ return filtered;
198
+ }
199
+ function applyHARFilters(endpoints, options) {
200
+ let filtered = endpoints;
201
+ // Filter by domains
202
+ if (options.filterDomains) {
203
+ const domains = options.filterDomains.split(',').map((d) => d.trim());
204
+ filtered = filtered.filter(ep => domains.some((domain) => ep.path.includes(domain) || (ep.description && ep.description.includes(domain))));
205
+ }
206
+ if (options.excludeDomains) {
207
+ const excludeDomains = options.excludeDomains.split(',').map((d) => d.trim());
208
+ filtered = filtered.filter(ep => !excludeDomains.some((domain) => ep.path.includes(domain) || (ep.description && ep.description.includes(domain))));
209
+ }
210
+ // Filter by methods
211
+ if (options.methods) {
212
+ const methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
213
+ filtered = filtered.filter(ep => methods.includes(ep.method));
214
+ }
215
+ return filtered;
216
+ }
217
+ function applyWSDLFilters(services, options) {
218
+ let filtered = services;
219
+ if (options.services) {
220
+ const serviceNames = options.services.split(',').map((s) => s.trim());
221
+ filtered = filtered.filter(svc => svc.name && serviceNames.some((name) => svc.name.includes(name)));
222
+ }
223
+ if (options.operations) {
224
+ const operationNames = options.operations.split(',').map((op) => op.trim());
225
+ filtered = filtered.filter(svc => svc.name && operationNames.some((name) => svc.name.includes(name)));
226
+ }
227
+ return filtered;
228
+ }
229
+ function applyPostmanFilters(requests, options) {
230
+ let filtered = requests;
231
+ if (options.folders) {
232
+ const folderNames = options.folders.split(',').map((f) => f.trim());
233
+ filtered = filtered.filter(req => req.folder && folderNames.includes(req.folder));
234
+ }
235
+ return filtered;
236
+ }
237
+ async function interactiveEndpointSelection(endpoints, type) {
238
+ // Group endpoints for better presentation
239
+ const grouped = groupEndpoints(endpoints, type);
240
+ const selected = [];
241
+ for (const [groupName, groupEndpoints] of Object.entries(grouped)) {
242
+ console.log(chalk_1.default.blue(`\n📁 ${groupName} (${groupEndpoints.length} ${type === 'wsdl' ? 'services' : 'endpoints'})`));
243
+ // Show endpoints in the group
244
+ groupEndpoints.forEach((ep, index) => {
245
+ const method = type === 'wsdl' ? 'SOAP' : ep.method;
246
+ console.log(` ${index + 1}. ${chalk_1.default.cyan(method.padEnd(6))} ${ep.path}`);
247
+ if (ep.description) {
248
+ console.log(` ${chalk_1.default.gray(ep.description.substring(0, 80))}${ep.description.length > 80 ? '...' : ''}`);
249
+ }
250
+ });
251
+ // Prompt for selection
252
+ const { selection } = await inquirer_1.default.prompt([{
253
+ type: 'input',
254
+ name: 'selection',
255
+ message: `Select from ${groupName} (numbers separated by commas, 'all', or 'skip'):`,
256
+ default: 'skip'
257
+ }]);
258
+ if (selection.toLowerCase() === 'skip')
259
+ continue;
260
+ if (selection.toLowerCase() === 'all') {
261
+ selected.push(...groupEndpoints);
262
+ continue;
263
+ }
264
+ // Parse selection
265
+ const indices = selection.split(',')
266
+ .map((s) => parseInt(s.trim()) - 1)
267
+ .filter((i) => i >= 0 && i < groupEndpoints.length);
268
+ for (const index of indices) {
269
+ selected.push(groupEndpoints[index]);
270
+ }
271
+ }
272
+ return selected;
273
+ }
274
+ function groupEndpoints(endpoints, type) {
275
+ const groups = {};
276
+ endpoints.forEach(endpoint => {
277
+ let groupName = 'Other';
278
+ switch (type) {
279
+ case 'openapi':
280
+ groupName = endpoint.tags && endpoint.tags.length > 0
281
+ ? endpoint.tags[0]
282
+ : extractPathGroup(endpoint.path);
283
+ break;
284
+ case 'har':
285
+ groupName = extractDomainFromEndpoint(endpoint);
286
+ break;
287
+ case 'wsdl':
288
+ groupName = endpoint.name ? extractServiceGroup(endpoint.name) : 'Unknown';
289
+ break;
290
+ case 'postman':
291
+ groupName = endpoint.folder || 'Root';
292
+ break;
293
+ }
294
+ if (!groups[groupName]) {
295
+ groups[groupName] = [];
296
+ }
297
+ groups[groupName].push(endpoint);
298
+ });
299
+ return groups;
300
+ }
301
+ function extractPathGroup(path) {
302
+ const parts = path.split('/').filter(p => p && !p.startsWith('{'));
303
+ return parts.length > 0 ? parts[0] : 'Root';
304
+ }
305
+ function extractDomainFromEndpoint(endpoint) {
306
+ if (endpoint.description && endpoint.description.includes('://')) {
307
+ try {
308
+ const url = new URL(endpoint.description.split(' ').find((part) => part.includes('://')) || '');
309
+ return url.hostname;
310
+ }
311
+ catch {
312
+ return 'Unknown';
313
+ }
314
+ }
315
+ return extractPathGroup(endpoint.path);
316
+ }
317
+ function extractServiceGroup(serviceName) {
318
+ const parts = serviceName.split('.');
319
+ return parts.length > 1 ? parts[0] : serviceName;
320
+ }
321
+ async function applyCorrelations(scenarios, _options) {
322
+ // Note: DataCorrelationAnalyzer is not available, so we'll skip this functionality for now
323
+ // You'll need to import and implement this class or remove this feature
324
+ console.log(chalk_1.default.yellow('Data correlation analysis is not available - skipping'));
325
+ return scenarios;
326
+ /* Commented out until DataCorrelationAnalyzer is available:
327
+ const analyzer = new DataCorrelationAnalyzer();
328
+ const correlatedScenarios: Scenario[] = [];
329
+
330
+ for (const scenario of scenarios) {
331
+ console.log(`Analyzing correlations for: ${scenario.name}`);
332
+
333
+ const dependencies = analyzer.analyzeSteps(scenario.steps);
334
+
335
+ if (dependencies.length > 0) {
336
+ console.log(` Found ${dependencies.length} potential correlations`);
337
+
338
+ if (options.interactive) {
339
+ // Interactive correlation review
340
+ const approvedDeps = await reviewCorrelations(dependencies, scenario.name);
341
+ scenario.steps = analyzer.applyCorrelations(scenario.steps, approvedDeps);
342
+ } else {
343
+ // Auto-apply with confidence threshold
344
+ const threshold = parseFloat(options.confidence || '0.6');
345
+ const highConfidenceDeps = dependencies.filter((dep: Dependency) =>
346
+ dep.required || (dep.description && dep.description.includes('confidence:') &&
347
+ parseFloat(dep.description.match(/confidence:\s*(\d*\.?\d+)/)?.[1] || '0') >= threshold)
348
+ );
349
+
350
+ if (highConfidenceDeps.length > 0) {
351
+ console.log(` Applying ${highConfidenceDeps.length} high-confidence correlations`);
352
+ scenario.steps = analyzer.applyCorrelations(scenario.steps, highConfidenceDeps);
353
+ }
354
+ }
355
+
356
+ // Generate correlation report
357
+ if (dependencies.length > 0 && options.output) {
358
+ const report = analyzer.generateCorrelationReport(dependencies);
359
+ const reportPath = path.join(options.output, `${scenario.name}-correlations.md`);
360
+ await ensureDirectory(path.dirname(reportPath));
361
+ await fs.writeFile(reportPath, report, 'utf8');
362
+ }
363
+ }
364
+
365
+ correlatedScenarios.push(scenario);
366
+ }
367
+
368
+ return correlatedScenarios;
369
+ */
370
+ }
371
+ async function reviewCorrelations(dependencies, scenarioName) {
372
+ console.log(chalk_1.default.blue(`\nReviewing correlations for: ${scenarioName}`));
373
+ const approved = [];
374
+ for (const dep of dependencies) {
375
+ console.log(chalk_1.default.yellow(`\nCorrelation: ${dep.sourceStep} → ${dep.targetStep}`));
376
+ console.log(` Extract: ${dep.sourceField}`);
377
+ console.log(` Use in: ${dep.targetLocation} as ${dep.targetField}`);
378
+ console.log(` ${dep.description}`);
379
+ const { apply } = await inquirer_1.default.prompt([{
380
+ type: 'confirm',
381
+ name: 'apply',
382
+ message: 'Apply this correlation?',
383
+ default: dep.required
384
+ }]);
385
+ if (apply) {
386
+ approved.push(dep);
387
+ }
388
+ }
389
+ return approved;
390
+ }
391
+ async function generateOutputFiles(scenarios, type, options) {
392
+ await ensureDirectory(options.output);
393
+ // Group scenarios into files based on scenariosPerFile option
394
+ const scenariosPerFile = parseInt(options.scenariosPerFile || '10');
395
+ const groups = [];
396
+ for (let i = 0; i < scenarios.length; i += scenariosPerFile) {
397
+ groups.push(scenarios.slice(i, i + scenariosPerFile));
398
+ }
399
+ // Determine output format - now supports typescript
400
+ const format = options.format === 'typescript'
401
+ ? 'typescript'
402
+ : options.format || 'yaml';
403
+ for (let i = 0; i < groups.length; i++) {
404
+ const group = groups[i];
405
+ const testName = groups.length > 1
406
+ ? `${type.toUpperCase()} Import (Part ${i + 1})`
407
+ : `${type.toUpperCase()} Import`;
408
+ // Determine file extension based on format
409
+ const extension = format === 'typescript' ? '.spec.ts' : `.${format}`;
410
+ const filename = groups.length > 1
411
+ ? `${type}-import-${i + 1}${extension}`
412
+ : `${type}-import${extension}`;
413
+ const initialPath = path.join(options.output, filename);
414
+ const filepath = await test_output_writer_1.TestOutputWriter.getSafeFilename(initialPath);
415
+ // Use the generic TestOutputWriter
416
+ const writer = new test_output_writer_1.TestOutputWriter({
417
+ name: testName,
418
+ description: `Generated test configuration from ${type.toUpperCase()} import`,
419
+ baseUrl: options.baseUrl || '{{env.BASE_URL}}',
420
+ scenarios: group,
421
+ format: format,
422
+ outputPath: filepath,
423
+ sourceType: type,
424
+ metadata: {
425
+ importedFrom: options.sourceFile,
426
+ importedAt: new Date().toISOString(),
427
+ totalEndpoints: scenarios.length,
428
+ groupIndex: i + 1,
429
+ totalGroups: groups.length,
430
+ filters: {
431
+ tags: options.tags,
432
+ excludeTags: options.excludeTags,
433
+ paths: options.paths,
434
+ methods: options.methods,
435
+ domains: options.filterDomains,
436
+ excludeDomains: options.excludeDomains,
437
+ services: options.services,
438
+ operations: options.operations,
439
+ folders: options.folders
440
+ }
441
+ }
442
+ }, {
443
+ // DSL options for importers
444
+ includeDataGeneration: true,
445
+ includeCSVSupport: true,
446
+ includeHooks: true,
447
+ includeCustomLogic: type !== 'wsdl', // Less custom logic for SOAP
448
+ includeMultipleLoadPatterns: true
449
+ });
450
+ const savedPath = await writer.write();
451
+ console.log(chalk_1.default.green(`✅ Generated: ${savedPath}`));
452
+ }
453
+ }
454
+ async function ensureDirectory(dirPath) {
455
+ try {
456
+ await fs.mkdir(dirPath, { recursive: true });
457
+ }
458
+ catch {
459
+ // Directory might already exist
460
+ }
461
+ }
@@ -0,0 +1,7 @@
1
+ export interface InitOptions {
2
+ template?: string;
3
+ examples?: boolean;
4
+ force?: boolean;
5
+ dryRun?: boolean;
6
+ }
7
+ export declare function initCommand(directory: string, options: InitOptions): Promise<void>;