@devicecloud.dev/dcd 4.1.3 → 4.1.5
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.
- package/dist/commands/cloud.d.ts +30 -0
- package/dist/commands/cloud.js +67 -3
- package/dist/config/flags/execution.flags.js +2 -2
- package/dist/gateways/api-gateway.d.ts +2 -2
- package/dist/gateways/api-gateway.js +1 -1
- package/dist/services/device-validation.service.d.ts +2 -0
- package/dist/services/device-validation.service.js +2 -0
- package/dist/services/execution-plan.service.d.ts +32 -1
- package/dist/services/execution-plan.service.js +43 -1
- package/dist/services/moropo.service.d.ts +5 -0
- package/dist/services/moropo.service.js +60 -55
- package/dist/services/report-download.service.d.ts +3 -0
- package/dist/services/report-download.service.js +3 -0
- package/dist/services/results-polling.service.d.ts +6 -0
- package/dist/services/results-polling.service.js +131 -117
- package/dist/services/test-submission.service.d.ts +6 -0
- package/dist/services/test-submission.service.js +56 -50
- package/dist/services/version.service.d.ts +3 -2
- package/dist/services/version.service.js +3 -2
- package/dist/types/generated/schema.types.d.ts +1086 -348
- package/oclif.manifest.json +4 -3
- package/package.json +1 -1
|
@@ -61,93 +61,16 @@ class ResultsPollingService {
|
|
|
61
61
|
logger(`DEBUG: Result status: ${result.test_file_name} - ${result.status}`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
const passed = statusCounts.PASSED || 0;
|
|
70
|
-
const failed = statusCounts.FAILED || 0;
|
|
71
|
-
const pending = statusCounts.PENDING || 0;
|
|
72
|
-
const running = statusCounts.RUNNING || 0;
|
|
73
|
-
const total = updatedResults.length;
|
|
74
|
-
const completed = passed + failed;
|
|
75
|
-
// Display quantitative summary in quiet mode only
|
|
76
|
-
const summary = `${completed}/${total} | ✓ ${passed} | ✗ ${failed} | ▶ ${running} | ⏸ ${pending}`;
|
|
77
|
-
if (!json) {
|
|
78
|
-
if (quiet) {
|
|
79
|
-
// Only update status when the summary changes in quiet mode
|
|
80
|
-
if (summary !== previousSummary) {
|
|
81
|
-
core_1.ux.action.status = summary;
|
|
82
|
-
previousSummary = summary;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// Show detailed table in non-quiet mode (no summary)
|
|
87
|
-
core_1.ux.action.status =
|
|
88
|
-
'\nStatus Test\n─────────── ───────────────';
|
|
89
|
-
for (const { retry_of: isRetry, status, test_file_name: test, } of updatedResults) {
|
|
90
|
-
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test} ${isRetry ? '(retry)' : ''}`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
64
|
+
const { summary } = this.calculateStatusSummary(updatedResults);
|
|
65
|
+
previousSummary = this.updateDisplayStatus(updatedResults, quiet, json, summary, previousSummary);
|
|
66
|
+
const allComplete = updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status));
|
|
67
|
+
if (allComplete) {
|
|
95
68
|
if (debug && logger) {
|
|
96
69
|
logger(`DEBUG: All tests completed, stopping poll`);
|
|
97
70
|
}
|
|
98
|
-
|
|
99
|
-
core_1.ux.action.stop('completed');
|
|
100
|
-
if (logger) {
|
|
101
|
-
logger('\n');
|
|
102
|
-
}
|
|
103
|
-
const hasFailedTests = updatedResults.some((result) => result.status === 'FAILED');
|
|
104
|
-
(0, cli_ux_1.table)(updatedResults, {
|
|
105
|
-
status: { get: (row) => row.status },
|
|
106
|
-
test: {
|
|
107
|
-
get: (row) => `${row.test_file_name} ${row.retry_of ? '(retry)' : ''}`,
|
|
108
|
-
},
|
|
109
|
-
duration: {
|
|
110
|
-
get: (row) => row.duration_seconds
|
|
111
|
-
? (0, methods_1.formatDurationSeconds)(Number(row.duration_seconds))
|
|
112
|
-
: '-',
|
|
113
|
-
},
|
|
114
|
-
...(hasFailedTests && {
|
|
115
|
-
// eslint-disable-next-line camelcase
|
|
116
|
-
fail_reason: {
|
|
117
|
-
get: (row) => row.status === 'FAILED' && row.fail_reason
|
|
118
|
-
? row.fail_reason
|
|
119
|
-
: '',
|
|
120
|
-
},
|
|
121
|
-
}),
|
|
122
|
-
}, { printLine: logger });
|
|
123
|
-
if (logger) {
|
|
124
|
-
logger('\n');
|
|
125
|
-
logger('Run completed, you can access the results at:');
|
|
126
|
-
logger(consoleUrl);
|
|
127
|
-
logger('\n');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
71
|
+
this.displayFinalResults(updatedResults, consoleUrl, json, logger);
|
|
130
72
|
clearInterval(intervalId);
|
|
131
|
-
const
|
|
132
|
-
const originalTryId = result.retry_of || result.id;
|
|
133
|
-
const tries = updatedResults.filter((r) => r.retry_of === originalTryId || r.id === originalTryId);
|
|
134
|
-
return result.id === Math.max(...tries.map((t) => t.id));
|
|
135
|
-
});
|
|
136
|
-
const output = {
|
|
137
|
-
consoleUrl,
|
|
138
|
-
status: resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')
|
|
139
|
-
? 'FAILED'
|
|
140
|
-
: 'PASSED',
|
|
141
|
-
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
142
|
-
durationSeconds: r.duration_seconds,
|
|
143
|
-
failReason: r.status === 'FAILED'
|
|
144
|
-
? r.fail_reason || 'No reason provided'
|
|
145
|
-
: undefined,
|
|
146
|
-
name: r.test_file_name,
|
|
147
|
-
status: r.status,
|
|
148
|
-
})),
|
|
149
|
-
uploadId,
|
|
150
|
-
};
|
|
73
|
+
const output = this.buildPollingResult(updatedResults, uploadId, consoleUrl);
|
|
151
74
|
if (output.status === 'FAILED') {
|
|
152
75
|
if (debug && logger) {
|
|
153
76
|
logger(`DEBUG: Some tests failed, returning failed status`);
|
|
@@ -165,46 +88,137 @@ class ResultsPollingService {
|
|
|
165
88
|
}
|
|
166
89
|
catch (error) {
|
|
167
90
|
sequentialPollFailures++;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
logger(`DEBUG: Sequential poll failures: ${sequentialPollFailures}`);
|
|
91
|
+
try {
|
|
92
|
+
await this.handlePollingError(error, sequentialPollFailures, debug, logger);
|
|
171
93
|
}
|
|
172
|
-
|
|
173
|
-
// dropped poll requests shouldn't err user CI
|
|
94
|
+
catch (error) {
|
|
174
95
|
clearInterval(intervalId);
|
|
175
|
-
|
|
176
|
-
if (debug && logger) {
|
|
177
|
-
logger('DEBUG: Checking internet connectivity...');
|
|
178
|
-
}
|
|
179
|
-
const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
|
|
180
|
-
if (debug && logger) {
|
|
181
|
-
logger(`DEBUG: ${connectivityCheck.message}`);
|
|
182
|
-
for (const result of connectivityCheck.endpointResults) {
|
|
183
|
-
if (result.success) {
|
|
184
|
-
logger(`DEBUG: ✓ ${result.endpoint} - ${result.statusCode} (${result.latencyMs}ms)`);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
logger(`DEBUG: ✗ ${result.endpoint} - ${result.error} (${result.latencyMs}ms)`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (!connectivityCheck.connected) {
|
|
192
|
-
// Build detailed error message with endpoint diagnostics
|
|
193
|
-
const endpointDetails = connectivityCheck.endpointResults
|
|
194
|
-
.map((r) => ` - ${r.endpoint}: ${r.error} (${r.latencyMs}ms)`)
|
|
195
|
-
.join('\n');
|
|
196
|
-
reject(new Error(`Unable to fetch results after ${this.MAX_SEQUENTIAL_FAILURES} attempts.\n\nInternet connectivity check failed - all test endpoints unreachable:\n${endpointDetails}\n\nPlease verify your network connection and DNS resolution.`));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
reject(new Error(`unable to fetch results after ${this.MAX_SEQUENTIAL_FAILURES} attempts`));
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (logger) {
|
|
203
|
-
logger('unable to fetch results, trying again...');
|
|
96
|
+
reject(error);
|
|
204
97
|
}
|
|
205
98
|
}
|
|
206
99
|
}, this.POLL_INTERVAL_MS);
|
|
207
100
|
});
|
|
208
101
|
}
|
|
102
|
+
buildPollingResult(results, uploadId, consoleUrl) {
|
|
103
|
+
const resultsWithoutEarlierTries = this.filterLatestResults(results);
|
|
104
|
+
return {
|
|
105
|
+
consoleUrl,
|
|
106
|
+
status: resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')
|
|
107
|
+
? 'FAILED'
|
|
108
|
+
: 'PASSED',
|
|
109
|
+
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
110
|
+
durationSeconds: r.duration_seconds,
|
|
111
|
+
failReason: r.status === 'FAILED' ? r.fail_reason || 'No reason provided' : undefined,
|
|
112
|
+
name: r.test_file_name,
|
|
113
|
+
status: r.status,
|
|
114
|
+
})),
|
|
115
|
+
uploadId,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
calculateStatusSummary(results) {
|
|
119
|
+
const statusCounts = {};
|
|
120
|
+
for (const result of results) {
|
|
121
|
+
statusCounts[result.status] = (statusCounts[result.status] || 0) + 1;
|
|
122
|
+
}
|
|
123
|
+
const passed = statusCounts.PASSED || 0;
|
|
124
|
+
const failed = statusCounts.FAILED || 0;
|
|
125
|
+
const pending = statusCounts.PENDING || 0;
|
|
126
|
+
const running = statusCounts.RUNNING || 0;
|
|
127
|
+
const total = results.length;
|
|
128
|
+
const completed = passed + failed;
|
|
129
|
+
const summary = `${completed}/${total} | ✓ ${passed} | ✗ ${failed} | ▶ ${running} | ⏸ ${pending}`;
|
|
130
|
+
return { completed, failed, passed, pending, running, summary, total };
|
|
131
|
+
}
|
|
132
|
+
displayFinalResults(results, consoleUrl, json, logger) {
|
|
133
|
+
if (json) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
core_1.ux.action.stop('completed');
|
|
137
|
+
if (logger) {
|
|
138
|
+
logger('\n');
|
|
139
|
+
}
|
|
140
|
+
const hasFailedTests = results.some((result) => result.status === 'FAILED');
|
|
141
|
+
(0, cli_ux_1.table)(results, {
|
|
142
|
+
duration: {
|
|
143
|
+
get: (row) => row.duration_seconds
|
|
144
|
+
? (0, methods_1.formatDurationSeconds)(Number(row.duration_seconds))
|
|
145
|
+
: '-',
|
|
146
|
+
},
|
|
147
|
+
status: { get: (row) => row.status },
|
|
148
|
+
test: {
|
|
149
|
+
get: (row) => `${row.test_file_name} ${row.retry_of ? '(retry)' : ''}`,
|
|
150
|
+
},
|
|
151
|
+
...(hasFailedTests && {
|
|
152
|
+
// eslint-disable-next-line camelcase
|
|
153
|
+
fail_reason: {
|
|
154
|
+
get: (row) => row.status === 'FAILED' && row.fail_reason ? row.fail_reason : '',
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
}, { printLine: logger });
|
|
158
|
+
if (logger) {
|
|
159
|
+
logger('\n');
|
|
160
|
+
logger('Run completed, you can access the results at:');
|
|
161
|
+
logger(consoleUrl);
|
|
162
|
+
logger('\n');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
filterLatestResults(results) {
|
|
166
|
+
return results.filter((result) => {
|
|
167
|
+
const originalTryId = result.retry_of || result.id;
|
|
168
|
+
const tries = results.filter((r) => r.retry_of === originalTryId || r.id === originalTryId);
|
|
169
|
+
return result.id === Math.max(...tries.map((t) => t.id));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async handlePollingError(error, sequentialPollFailures, debug, logger) {
|
|
173
|
+
if (debug && logger) {
|
|
174
|
+
logger(`DEBUG: Error polling for results: ${error}`);
|
|
175
|
+
logger(`DEBUG: Sequential poll failures: ${sequentialPollFailures}`);
|
|
176
|
+
}
|
|
177
|
+
if (sequentialPollFailures > this.MAX_SEQUENTIAL_FAILURES) {
|
|
178
|
+
if (debug && logger) {
|
|
179
|
+
logger('DEBUG: Checking internet connectivity...');
|
|
180
|
+
}
|
|
181
|
+
const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
|
|
182
|
+
if (debug && logger) {
|
|
183
|
+
logger(`DEBUG: ${connectivityCheck.message}`);
|
|
184
|
+
for (const result of connectivityCheck.endpointResults) {
|
|
185
|
+
if (result.success) {
|
|
186
|
+
logger(`DEBUG: ✓ ${result.endpoint} - ${result.statusCode} (${result.latencyMs}ms)`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
logger(`DEBUG: ✗ ${result.endpoint} - ${result.error} (${result.latencyMs}ms)`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!connectivityCheck.connected) {
|
|
194
|
+
const endpointDetails = connectivityCheck.endpointResults
|
|
195
|
+
.map((r) => ` - ${r.endpoint}: ${r.error} (${r.latencyMs}ms)`)
|
|
196
|
+
.join('\n');
|
|
197
|
+
throw new Error(`Unable to fetch results after ${this.MAX_SEQUENTIAL_FAILURES} attempts.\n\nInternet connectivity check failed - all test endpoints unreachable:\n${endpointDetails}\n\nPlease verify your network connection and DNS resolution.`);
|
|
198
|
+
}
|
|
199
|
+
throw new Error(`unable to fetch results after ${this.MAX_SEQUENTIAL_FAILURES} attempts`);
|
|
200
|
+
}
|
|
201
|
+
if (logger) {
|
|
202
|
+
logger('unable to fetch results, trying again...');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
updateDisplayStatus(results, quiet, json, summary, previousSummary) {
|
|
206
|
+
if (json) {
|
|
207
|
+
return previousSummary;
|
|
208
|
+
}
|
|
209
|
+
if (quiet) {
|
|
210
|
+
if (summary !== previousSummary) {
|
|
211
|
+
core_1.ux.action.status = summary;
|
|
212
|
+
return summary;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
core_1.ux.action.status = '\nStatus Test\n─────────── ───────────────';
|
|
217
|
+
for (const { retry_of: isRetry, status, test_file_name: test, } of results) {
|
|
218
|
+
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test} ${isRetry ? '(retry)' : ''}`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return previousSummary;
|
|
222
|
+
}
|
|
209
223
|
}
|
|
210
224
|
exports.ResultsPollingService = ResultsPollingService;
|
|
@@ -38,4 +38,10 @@ export declare class TestSubmissionService {
|
|
|
38
38
|
* @returns FormData ready to be submitted to the API
|
|
39
39
|
*/
|
|
40
40
|
buildTestFormData(config: TestSubmissionConfig): Promise<FormData>;
|
|
41
|
+
private logDebug;
|
|
42
|
+
private normalizeFilePath;
|
|
43
|
+
private normalizePathMap;
|
|
44
|
+
private normalizePaths;
|
|
45
|
+
private parseKeyValuePairs;
|
|
46
|
+
private setOptionalFields;
|
|
41
47
|
}
|
|
@@ -20,29 +20,15 @@ class TestSubmissionService {
|
|
|
20
20
|
const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
|
|
21
21
|
const { flows: sequentialFlows = [] } = sequence ?? {};
|
|
22
22
|
const testFormData = new FormData();
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
acc[key] = value.join('=');
|
|
28
|
-
return acc;
|
|
29
|
-
}, {});
|
|
30
|
-
// eslint-disable-next-line unicorn/no-array-reduce
|
|
31
|
-
const metadataObject = metadata.reduce((acc, cur) => {
|
|
32
|
-
const [key, ...value] = cur.split('=');
|
|
33
|
-
// handle case where value includes an equals sign
|
|
34
|
-
acc[key] = value.join('=');
|
|
35
|
-
return acc;
|
|
36
|
-
}, {});
|
|
37
|
-
if (debug && logger && Object.keys(envObject).length > 0) {
|
|
38
|
-
logger(`DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
|
|
39
|
-
}
|
|
40
|
-
if (debug && logger && Object.keys(metadataObject).length > 0) {
|
|
41
|
-
logger(`DEBUG: User metadata: ${JSON.stringify(metadataObject)}`);
|
|
23
|
+
const envObject = this.parseKeyValuePairs(env);
|
|
24
|
+
const metadataObject = this.parseKeyValuePairs(metadata);
|
|
25
|
+
if (Object.keys(envObject).length > 0) {
|
|
26
|
+
this.logDebug(debug, logger, `DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
|
|
42
27
|
}
|
|
43
|
-
if (
|
|
44
|
-
logger
|
|
28
|
+
if (Object.keys(metadataObject).length > 0) {
|
|
29
|
+
this.logDebug(debug, logger, `DEBUG: User metadata: ${JSON.stringify(metadataObject)}`);
|
|
45
30
|
}
|
|
31
|
+
this.logDebug(debug, logger, `DEBUG: Compressing files from path: ${flowFile}`);
|
|
46
32
|
const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
|
|
47
33
|
? path.dirname(flowFile)
|
|
48
34
|
: flowFile, [
|
|
@@ -52,24 +38,16 @@ class TestSubmissionService {
|
|
|
52
38
|
...sequentialFlows,
|
|
53
39
|
]),
|
|
54
40
|
], commonRoot);
|
|
55
|
-
|
|
56
|
-
logger(`DEBUG: Compressed file size: ${buffer.length} bytes`);
|
|
57
|
-
}
|
|
41
|
+
this.logDebug(debug, logger, `DEBUG: Compressed file size: ${buffer.length} bytes`);
|
|
58
42
|
const blob = new Blob([buffer], {
|
|
59
43
|
type: mimeTypeLookupByExtension.zip,
|
|
60
44
|
});
|
|
61
45
|
testFormData.set('file', blob, 'flowFile.zip');
|
|
62
46
|
testFormData.set('appBinaryId', appBinaryId);
|
|
63
|
-
testFormData.set('testFileNames', JSON.stringify(
|
|
64
|
-
testFormData.set('flowMetadata', JSON.stringify(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
]))));
|
|
68
|
-
testFormData.set('testFileOverrides', JSON.stringify(Object.fromEntries(Object.entries(flowOverrides).map(([key, value]) => [
|
|
69
|
-
key.replaceAll(commonRoot, '.').split(path.sep).join('/'),
|
|
70
|
-
value,
|
|
71
|
-
]))));
|
|
72
|
-
testFormData.set('sequentialFlows', JSON.stringify(sequentialFlows.map((t) => t.replaceAll(commonRoot, '.').split(path.sep).join('/'))));
|
|
47
|
+
testFormData.set('testFileNames', JSON.stringify(this.normalizePaths(testFileNames, commonRoot)));
|
|
48
|
+
testFormData.set('flowMetadata', JSON.stringify(this.normalizePathMap(flowMetadata, commonRoot)));
|
|
49
|
+
testFormData.set('testFileOverrides', JSON.stringify(this.normalizePathMap(flowOverrides, commonRoot)));
|
|
50
|
+
testFormData.set('sequentialFlows', JSON.stringify(this.normalizePaths(sequentialFlows, commonRoot)));
|
|
73
51
|
testFormData.set('env', JSON.stringify(envObject));
|
|
74
52
|
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
75
53
|
const configPayload = {
|
|
@@ -92,25 +70,53 @@ class TestSubmissionService {
|
|
|
92
70
|
if (Object.keys(metadataObject).length > 0) {
|
|
93
71
|
const metadataPayload = { userMetadata: metadataObject };
|
|
94
72
|
testFormData.set('metadata', JSON.stringify(metadataPayload));
|
|
95
|
-
|
|
96
|
-
logger(`DEBUG: Sending metadata to API: ${JSON.stringify(metadataPayload)}`);
|
|
97
|
-
}
|
|
73
|
+
this.logDebug(debug, logger, `DEBUG: Sending metadata to API: ${JSON.stringify(metadataPayload)}`);
|
|
98
74
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
testFormData.set('name', name.toString());
|
|
109
|
-
if (runnerType)
|
|
110
|
-
testFormData.set('runnerType', runnerType.toString());
|
|
111
|
-
if (workspaceConfig)
|
|
75
|
+
this.setOptionalFields(testFormData, {
|
|
76
|
+
androidApiLevel,
|
|
77
|
+
androidDevice,
|
|
78
|
+
iOSDevice,
|
|
79
|
+
iOSVersion,
|
|
80
|
+
name,
|
|
81
|
+
runnerType,
|
|
82
|
+
});
|
|
83
|
+
if (workspaceConfig) {
|
|
112
84
|
testFormData.set('workspaceConfig', JSON.stringify(workspaceConfig));
|
|
85
|
+
}
|
|
113
86
|
return testFormData;
|
|
114
87
|
}
|
|
88
|
+
logDebug(debug, logger, message) {
|
|
89
|
+
if (debug && logger) {
|
|
90
|
+
logger(message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
normalizeFilePath(filePath, commonRoot) {
|
|
94
|
+
return filePath.replaceAll(commonRoot, '.').split(path.sep).join('/');
|
|
95
|
+
}
|
|
96
|
+
normalizePathMap(map, commonRoot) {
|
|
97
|
+
return Object.fromEntries(Object.entries(map).map(([key, value]) => [
|
|
98
|
+
this.normalizeFilePath(key, commonRoot),
|
|
99
|
+
value,
|
|
100
|
+
]));
|
|
101
|
+
}
|
|
102
|
+
normalizePaths(paths, commonRoot) {
|
|
103
|
+
return paths.map((p) => this.normalizeFilePath(p, commonRoot));
|
|
104
|
+
}
|
|
105
|
+
parseKeyValuePairs(pairs) {
|
|
106
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
107
|
+
return pairs.reduce((acc, cur) => {
|
|
108
|
+
const [key, ...value] = cur.split('=');
|
|
109
|
+
// handle case where value includes an equals sign
|
|
110
|
+
acc[key] = value.join('=');
|
|
111
|
+
return acc;
|
|
112
|
+
}, {});
|
|
113
|
+
}
|
|
114
|
+
setOptionalFields(formData, fields) {
|
|
115
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
116
|
+
if (value) {
|
|
117
|
+
formData.set(key, value.toString());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
115
121
|
}
|
|
116
122
|
exports.TestSubmissionService = TestSubmissionService;
|
|
@@ -19,8 +19,9 @@ export declare class VersionService {
|
|
|
19
19
|
* Resolve and validate Maestro version against API compatibility data
|
|
20
20
|
* @param requestedVersion - Version requested by user (or undefined for default)
|
|
21
21
|
* @param compatibilityData - API compatibility data
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
22
|
+
* @param options - Configuration options
|
|
23
|
+
* @param options.debug - Enable debug logging
|
|
24
|
+
* @param options.logger - Optional logger function
|
|
24
25
|
* @returns Validated Maestro version string
|
|
25
26
|
* @throws Error if version is not supported
|
|
26
27
|
*/
|
|
@@ -44,8 +44,9 @@ class VersionService {
|
|
|
44
44
|
* Resolve and validate Maestro version against API compatibility data
|
|
45
45
|
* @param requestedVersion - Version requested by user (or undefined for default)
|
|
46
46
|
* @param compatibilityData - API compatibility data
|
|
47
|
-
* @param
|
|
48
|
-
* @param
|
|
47
|
+
* @param options - Configuration options
|
|
48
|
+
* @param options.debug - Enable debug logging
|
|
49
|
+
* @param options.logger - Optional logger function
|
|
49
50
|
* @returns Validated Maestro version string
|
|
50
51
|
* @throws Error if version is not supported
|
|
51
52
|
*/
|