@devicecloud.dev/dcd 4.1.5 → 4.1.7
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.js +48 -44
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.js +10 -33
- package/dist/commands/upload.d.ts +1 -0
- package/dist/commands/upload.js +11 -9
- package/dist/config/flags/execution.flags.js +1 -1
- package/dist/gateways/api-gateway.d.ts +18 -2
- package/dist/gateways/api-gateway.js +18 -4
- package/dist/gateways/supabase-gateway.d.ts +1 -1
- package/dist/gateways/supabase-gateway.js +68 -9
- package/dist/methods.d.ts +1 -1
- package/dist/methods.js +467 -27
- package/dist/services/results-polling.service.d.ts +30 -0
- package/dist/services/results-polling.service.js +167 -71
- package/dist/services/test-submission.service.js +11 -0
- package/dist/types/generated/schema.types.d.ts +15 -0
- package/dist/types/schema.types.d.ts +1523 -0
- package/dist/types/schema.types.js +3 -0
- package/dist/utils/styling.d.ts +106 -0
- package/dist/utils/styling.js +166 -0
- package/oclif.manifest.json +8 -2
- package/package.json +16 -15
|
@@ -6,6 +6,7 @@ const cli_ux_1 = require("@oclif/core/lib/cli-ux");
|
|
|
6
6
|
const api_gateway_1 = require("../gateways/api-gateway");
|
|
7
7
|
const methods_1 = require("../methods");
|
|
8
8
|
const connectivity_1 = require("../utils/connectivity");
|
|
9
|
+
const styling_1 = require("../utils/styling");
|
|
9
10
|
/**
|
|
10
11
|
* Custom error for run failures that includes the polling result
|
|
11
12
|
*/
|
|
@@ -32,72 +33,46 @@ class ResultsPollingService {
|
|
|
32
33
|
*/
|
|
33
34
|
async pollUntilComplete(results, options) {
|
|
34
35
|
const { apiUrl, apiKey, uploadId, consoleUrl, quiet = false, json = false, debug = false, logger } = options;
|
|
35
|
-
|
|
36
|
-
core_1.ux.action.start('Waiting for results', 'Initializing', {
|
|
37
|
-
stdout: true,
|
|
38
|
-
});
|
|
39
|
-
if (logger) {
|
|
40
|
-
logger('\nYou can safely close this terminal and the tests will continue\n');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
36
|
+
this.initializePollingDisplay(json, logger);
|
|
43
37
|
let sequentialPollFailures = 0;
|
|
44
38
|
let previousSummary = '';
|
|
45
39
|
if (debug && logger) {
|
|
46
40
|
logger(`DEBUG: Starting polling loop for results`);
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
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) {
|
|
68
|
-
if (debug && logger) {
|
|
69
|
-
logger(`DEBUG: All tests completed, stopping poll`);
|
|
70
|
-
}
|
|
71
|
-
this.displayFinalResults(updatedResults, consoleUrl, json, logger);
|
|
72
|
-
clearInterval(intervalId);
|
|
73
|
-
const output = this.buildPollingResult(updatedResults, uploadId, consoleUrl);
|
|
74
|
-
if (output.status === 'FAILED') {
|
|
75
|
-
if (debug && logger) {
|
|
76
|
-
logger(`DEBUG: Some tests failed, returning failed status`);
|
|
77
|
-
}
|
|
78
|
-
reject(new RunFailedError(output));
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
if (debug && logger) {
|
|
82
|
-
logger(`DEBUG: All tests passed, returning success status`);
|
|
83
|
-
}
|
|
84
|
-
sequentialPollFailures = 0;
|
|
85
|
-
resolve(output);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
42
|
+
// Poll in a loop until all tests complete
|
|
43
|
+
// eslint-disable-next-line no-constant-condition
|
|
44
|
+
while (true) {
|
|
45
|
+
try {
|
|
46
|
+
const updatedResults = await this.fetchAndLogResults(apiUrl, apiKey, uploadId, debug, logger);
|
|
47
|
+
const { summary } = this.calculateStatusSummary(updatedResults);
|
|
48
|
+
previousSummary = this.updateDisplayStatus(updatedResults, quiet, json, summary, previousSummary);
|
|
49
|
+
const allComplete = updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status));
|
|
50
|
+
if (allComplete) {
|
|
51
|
+
return await this.handleCompletedTests(updatedResults, {
|
|
52
|
+
consoleUrl,
|
|
53
|
+
debug,
|
|
54
|
+
json,
|
|
55
|
+
logger,
|
|
56
|
+
uploadId,
|
|
57
|
+
});
|
|
88
58
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
59
|
+
// Reset failure counter on successful poll
|
|
60
|
+
sequentialPollFailures = 0;
|
|
61
|
+
// Wait before next poll
|
|
62
|
+
await this.sleep(this.POLL_INTERVAL_MS);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Re-throw RunFailedError immediately (test failures, not polling errors)
|
|
66
|
+
if (error instanceof RunFailedError) {
|
|
67
|
+
throw error;
|
|
98
68
|
}
|
|
99
|
-
|
|
100
|
-
|
|
69
|
+
sequentialPollFailures++;
|
|
70
|
+
// Handle polling errors (network issues, etc.)
|
|
71
|
+
await this.handlePollingError(error, sequentialPollFailures, debug, logger);
|
|
72
|
+
// Wait before retrying after an error
|
|
73
|
+
await this.sleep(this.POLL_INTERVAL_MS);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
101
76
|
}
|
|
102
77
|
buildPollingResult(results, uploadId, consoleUrl) {
|
|
103
78
|
const resultsWithoutEarlierTries = this.filterLatestResults(results);
|
|
@@ -126,42 +101,105 @@ class ResultsPollingService {
|
|
|
126
101
|
const running = statusCounts.RUNNING || 0;
|
|
127
102
|
const total = results.length;
|
|
128
103
|
const completed = passed + failed;
|
|
129
|
-
const summary =
|
|
104
|
+
const summary = (0, styling_1.formatTestSummary)({
|
|
105
|
+
completed,
|
|
106
|
+
failed,
|
|
107
|
+
passed,
|
|
108
|
+
pending,
|
|
109
|
+
running,
|
|
110
|
+
total,
|
|
111
|
+
});
|
|
130
112
|
return { completed, failed, passed, pending, running, summary, total };
|
|
131
113
|
}
|
|
132
114
|
displayFinalResults(results, consoleUrl, json, logger) {
|
|
133
115
|
if (json) {
|
|
134
116
|
return;
|
|
135
117
|
}
|
|
136
|
-
core_1.ux.action.stop('completed');
|
|
118
|
+
core_1.ux.action.stop(styling_1.colors.success('completed'));
|
|
137
119
|
if (logger) {
|
|
138
120
|
logger('\n');
|
|
139
121
|
}
|
|
140
122
|
const hasFailedTests = results.some((result) => result.status === 'FAILED');
|
|
141
123
|
(0, cli_ux_1.table)(results, {
|
|
142
124
|
duration: {
|
|
143
|
-
get
|
|
144
|
-
|
|
145
|
-
|
|
125
|
+
get(row) {
|
|
126
|
+
return row.duration_seconds
|
|
127
|
+
? styling_1.colors.dim((0, methods_1.formatDurationSeconds)(Number(row.duration_seconds)))
|
|
128
|
+
: styling_1.colors.dim('-');
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
status: {
|
|
132
|
+
get(row) {
|
|
133
|
+
const statusUpper = row.status.toUpperCase();
|
|
134
|
+
switch (statusUpper) {
|
|
135
|
+
case 'PASSED': {
|
|
136
|
+
return styling_1.colors.success(row.status);
|
|
137
|
+
}
|
|
138
|
+
case 'FAILED': {
|
|
139
|
+
return styling_1.colors.error(row.status);
|
|
140
|
+
}
|
|
141
|
+
case 'RUNNING': {
|
|
142
|
+
return styling_1.colors.info(row.status);
|
|
143
|
+
}
|
|
144
|
+
case 'PENDING': {
|
|
145
|
+
return styling_1.colors.warning(row.status);
|
|
146
|
+
}
|
|
147
|
+
default: {
|
|
148
|
+
return styling_1.colors.dim(row.status);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
146
152
|
},
|
|
147
|
-
status: { get: (row) => row.status },
|
|
148
153
|
test: {
|
|
149
|
-
get
|
|
154
|
+
get(row) {
|
|
155
|
+
const testName = row.test_file_name;
|
|
156
|
+
const retry = row.retry_of ? styling_1.colors.dim(' (retry)') : '';
|
|
157
|
+
return `${testName}${retry}`;
|
|
158
|
+
},
|
|
150
159
|
},
|
|
151
160
|
...(hasFailedTests && {
|
|
152
161
|
// eslint-disable-next-line camelcase
|
|
153
162
|
fail_reason: {
|
|
154
|
-
get
|
|
163
|
+
get(row) {
|
|
164
|
+
return row.status === 'FAILED' && row.fail_reason
|
|
165
|
+
? styling_1.colors.error(row.fail_reason)
|
|
166
|
+
: '';
|
|
167
|
+
},
|
|
155
168
|
},
|
|
156
169
|
}),
|
|
157
170
|
}, { printLine: logger });
|
|
158
171
|
if (logger) {
|
|
159
172
|
logger('\n');
|
|
160
|
-
logger('Run completed, you can access the results at:');
|
|
161
|
-
logger(consoleUrl);
|
|
173
|
+
logger(styling_1.colors.bold('Run completed') + styling_1.colors.dim(', you can access the results at:'));
|
|
174
|
+
logger(styling_1.colors.url(consoleUrl));
|
|
162
175
|
logger('\n');
|
|
163
176
|
}
|
|
164
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Fetch results from API and log debug information
|
|
180
|
+
* @param apiUrl API base URL
|
|
181
|
+
* @param apiKey API authentication key
|
|
182
|
+
* @param uploadId Upload ID to fetch results for
|
|
183
|
+
* @param debug Whether debug logging is enabled
|
|
184
|
+
* @param logger Optional logger function
|
|
185
|
+
* @returns Promise resolving to test results
|
|
186
|
+
*/
|
|
187
|
+
async fetchAndLogResults(apiUrl, apiKey, uploadId, debug, logger) {
|
|
188
|
+
if (debug && logger) {
|
|
189
|
+
logger(`DEBUG: Polling for results: ${uploadId}`);
|
|
190
|
+
}
|
|
191
|
+
const { results: updatedResults } = await api_gateway_1.ApiGateway.getResultsForUpload(apiUrl, apiKey, uploadId);
|
|
192
|
+
if (!updatedResults) {
|
|
193
|
+
throw new Error('no results');
|
|
194
|
+
}
|
|
195
|
+
if (debug && logger) {
|
|
196
|
+
logger(`DEBUG: Poll received ${updatedResults.length} results`);
|
|
197
|
+
for (const result of updatedResults) {
|
|
198
|
+
logger(`DEBUG: Result status: ${result.test_file_name} - ${result.status}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return updatedResults;
|
|
202
|
+
}
|
|
165
203
|
filterLatestResults(results) {
|
|
166
204
|
return results.filter((result) => {
|
|
167
205
|
const originalTryId = result.retry_of || result.id;
|
|
@@ -169,6 +207,30 @@ class ResultsPollingService {
|
|
|
169
207
|
return result.id === Math.max(...tries.map((t) => t.id));
|
|
170
208
|
});
|
|
171
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Handle completed tests and return final result
|
|
212
|
+
* @param updatedResults Test results from API
|
|
213
|
+
* @param options Completion handling options
|
|
214
|
+
* @returns Promise resolving to final polling result
|
|
215
|
+
*/
|
|
216
|
+
async handleCompletedTests(updatedResults, options) {
|
|
217
|
+
const { uploadId, consoleUrl, json, debug, logger } = options;
|
|
218
|
+
if (debug && logger) {
|
|
219
|
+
logger(`DEBUG: All tests completed, stopping poll`);
|
|
220
|
+
}
|
|
221
|
+
this.displayFinalResults(updatedResults, consoleUrl, json, logger);
|
|
222
|
+
const output = this.buildPollingResult(updatedResults, uploadId, consoleUrl);
|
|
223
|
+
if (output.status === 'FAILED') {
|
|
224
|
+
if (debug && logger) {
|
|
225
|
+
logger(`DEBUG: Some tests failed, returning failed status`);
|
|
226
|
+
}
|
|
227
|
+
throw new RunFailedError(output);
|
|
228
|
+
}
|
|
229
|
+
if (debug && logger) {
|
|
230
|
+
logger(`DEBUG: All tests passed, returning success status`);
|
|
231
|
+
}
|
|
232
|
+
return output;
|
|
233
|
+
}
|
|
172
234
|
async handlePollingError(error, sequentialPollFailures, debug, logger) {
|
|
173
235
|
if (debug && logger) {
|
|
174
236
|
logger(`DEBUG: Error polling for results: ${error}`);
|
|
@@ -202,6 +264,32 @@ class ResultsPollingService {
|
|
|
202
264
|
logger('unable to fetch results, trying again...');
|
|
203
265
|
}
|
|
204
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Initialize the polling display UI
|
|
269
|
+
* @param json Whether to output in JSON format
|
|
270
|
+
* @param logger Optional logger function for output
|
|
271
|
+
* @returns void
|
|
272
|
+
*/
|
|
273
|
+
initializePollingDisplay(json, logger) {
|
|
274
|
+
if (!json) {
|
|
275
|
+
core_1.ux.action.start(styling_1.colors.bold('Waiting for results'), styling_1.colors.dim('Initializing'), {
|
|
276
|
+
stdout: true,
|
|
277
|
+
});
|
|
278
|
+
if (logger) {
|
|
279
|
+
logger(styling_1.colors.dim('\nYou can safely close this terminal and the tests will continue\n'));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Sleep for the specified number of milliseconds
|
|
285
|
+
* @param ms Number of milliseconds to sleep
|
|
286
|
+
* @returns Promise that resolves after the delay
|
|
287
|
+
*/
|
|
288
|
+
async sleep(ms) {
|
|
289
|
+
return new Promise((resolve) => {
|
|
290
|
+
setTimeout(resolve, ms);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
205
293
|
updateDisplayStatus(results, quiet, json, summary, previousSummary) {
|
|
206
294
|
if (json) {
|
|
207
295
|
return previousSummary;
|
|
@@ -213,9 +301,17 @@ class ResultsPollingService {
|
|
|
213
301
|
}
|
|
214
302
|
}
|
|
215
303
|
else {
|
|
216
|
-
core_1.ux.action.status = '\nStatus Test\n─────────── ───────────────';
|
|
304
|
+
core_1.ux.action.status = styling_1.colors.dim('\nStatus Test\n─────────── ───────────────');
|
|
217
305
|
for (const { retry_of: isRetry, status, test_file_name: test, } of results) {
|
|
218
|
-
|
|
306
|
+
const statusFormatted = status.toUpperCase() === 'PASSED'
|
|
307
|
+
? styling_1.colors.success(status.padEnd(10, ' '))
|
|
308
|
+
: status.toUpperCase() === 'FAILED'
|
|
309
|
+
? styling_1.colors.error(status.padEnd(10, ' '))
|
|
310
|
+
: status.toUpperCase() === 'RUNNING'
|
|
311
|
+
? styling_1.colors.info(status.padEnd(10, ' '))
|
|
312
|
+
: styling_1.colors.warning(status.padEnd(10, ' '));
|
|
313
|
+
const retryText = isRetry ? styling_1.colors.dim(' (retry)') : '';
|
|
314
|
+
core_1.ux.action.status += `\n${statusFormatted} ${test}${retryText}`;
|
|
219
315
|
}
|
|
220
316
|
}
|
|
221
317
|
return previousSummary;
|
|
@@ -28,6 +28,17 @@ class TestSubmissionService {
|
|
|
28
28
|
if (Object.keys(metadataObject).length > 0) {
|
|
29
29
|
this.logDebug(debug, logger, `DEBUG: User metadata: ${JSON.stringify(metadataObject)}`);
|
|
30
30
|
}
|
|
31
|
+
// Log non-YAML file assets being uploaded
|
|
32
|
+
if (referencedFiles.length > 0) {
|
|
33
|
+
const nonYamlFiles = referencedFiles.filter((file) => !file.endsWith('.yaml') && !file.endsWith('.yml'));
|
|
34
|
+
if (nonYamlFiles.length > 0) {
|
|
35
|
+
this.logDebug(debug, logger, `DEBUG: Uploading ${nonYamlFiles.length} non-YAML file asset(s):`);
|
|
36
|
+
for (const file of nonYamlFiles) {
|
|
37
|
+
const normalizedPath = this.normalizeFilePath(file, commonRoot);
|
|
38
|
+
this.logDebug(debug, logger, `DEBUG: - ${normalizedPath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
this.logDebug(debug, logger, `DEBUG: Compressing files from path: ${flowFile}`);
|
|
32
43
|
const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
|
|
33
44
|
? path.dirname(flowFile)
|
|
@@ -443,6 +443,21 @@ export interface components {
|
|
|
443
443
|
path: string;
|
|
444
444
|
token: string;
|
|
445
445
|
id: string;
|
|
446
|
+
b2?: {
|
|
447
|
+
strategy: 'simple' | 'large';
|
|
448
|
+
simple?: {
|
|
449
|
+
uploadUrl: string;
|
|
450
|
+
authorizationToken: string;
|
|
451
|
+
};
|
|
452
|
+
large?: {
|
|
453
|
+
fileId: string;
|
|
454
|
+
fileName: string;
|
|
455
|
+
uploadPartUrls: Array<{
|
|
456
|
+
uploadUrl: string;
|
|
457
|
+
authorizationToken: string;
|
|
458
|
+
}>;
|
|
459
|
+
};
|
|
460
|
+
};
|
|
446
461
|
};
|
|
447
462
|
ICheckForExistingUploadArgs: {
|
|
448
463
|
sha: string;
|