@devicecloud.dev/dcd 4.1.4 → 4.1.6
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 +78 -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 +2 -2
- package/dist/gateways/api-gateway.d.ts +2 -2
- package/dist/gateways/supabase-gateway.d.ts +1 -1
- package/dist/gateways/supabase-gateway.js +51 -9
- package/dist/methods.d.ts +1 -1
- package/dist/methods.js +183 -25
- package/dist/services/execution-plan.service.d.ts +23 -0
- package/dist/services/execution-plan.service.js +41 -0
- 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 +1086 -348
- 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 +10 -3
- package/package.json +16 -15
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Primary CLI command for executing tests on DeviceCloud.
|
|
4
|
+
* Orchestrates the complete test workflow:
|
|
5
|
+
* - Binary upload with SHA deduplication
|
|
6
|
+
* - Flow file analysis and dependency resolution
|
|
7
|
+
* - Device compatibility validation
|
|
8
|
+
* - Test submission with parallel execution
|
|
9
|
+
* - Real-time result polling with 5-second intervals
|
|
10
|
+
* - Artifact download (reports, videos, logs)
|
|
11
|
+
*
|
|
12
|
+
* Replaces `maestro cloud` with DeviceCloud-specific functionality.
|
|
13
|
+
*/
|
|
2
14
|
export default class Cloud extends Command {
|
|
3
15
|
static args: {
|
|
4
16
|
firstFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -50,12 +62,30 @@ export default class Cloud extends Command {
|
|
|
50
62
|
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
63
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
52
64
|
};
|
|
65
|
+
/** Service for device/OS compatibility validation */
|
|
53
66
|
private deviceValidationService;
|
|
67
|
+
/** Service for Moropo test framework integration */
|
|
54
68
|
private moropoService;
|
|
69
|
+
/** Service for downloading test reports and artifacts */
|
|
55
70
|
private reportDownloadService;
|
|
71
|
+
/** Service for polling test results with 5-second intervals */
|
|
56
72
|
private resultsPollingService;
|
|
73
|
+
/** Service for submitting tests to the API */
|
|
57
74
|
private testSubmissionService;
|
|
75
|
+
/**
|
|
76
|
+
* Check for CLI updates and notify user if outdated
|
|
77
|
+
* Compares current version with latest npm release
|
|
78
|
+
* @returns Promise that resolves when version check is complete
|
|
79
|
+
*/
|
|
58
80
|
private versionCheck;
|
|
81
|
+
/** Service for CLI version checking */
|
|
59
82
|
private versionService;
|
|
83
|
+
/**
|
|
84
|
+
* Main command execution entry point
|
|
85
|
+
* Orchestrates the complete test submission and monitoring workflow
|
|
86
|
+
* @returns Promise that resolves when command execution is complete
|
|
87
|
+
* @throws RunFailedError if tests fail
|
|
88
|
+
* @throws Error for infrastructure or configuration errors
|
|
89
|
+
*/
|
|
60
90
|
run(): Promise<any>;
|
|
61
91
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -15,6 +15,7 @@ const results_polling_service_1 = require("../services/results-polling.service")
|
|
|
15
15
|
const test_submission_service_1 = require("../services/test-submission.service");
|
|
16
16
|
const version_service_1 = require("../services/version.service");
|
|
17
17
|
const compatibility_1 = require("../utils/compatibility");
|
|
18
|
+
const styling_1 = require("../utils/styling");
|
|
18
19
|
// Suppress punycode deprecation warning (caused by whatwg, supabase dependancy)
|
|
19
20
|
process.removeAllListeners('warning');
|
|
20
21
|
process.on('warning', (warning) => {
|
|
@@ -23,6 +24,18 @@ process.on('warning', (warning) => {
|
|
|
23
24
|
// Ignore punycode deprecation warnings
|
|
24
25
|
}
|
|
25
26
|
});
|
|
27
|
+
/**
|
|
28
|
+
* Primary CLI command for executing tests on DeviceCloud.
|
|
29
|
+
* Orchestrates the complete test workflow:
|
|
30
|
+
* - Binary upload with SHA deduplication
|
|
31
|
+
* - Flow file analysis and dependency resolution
|
|
32
|
+
* - Device compatibility validation
|
|
33
|
+
* - Test submission with parallel execution
|
|
34
|
+
* - Real-time result polling with 5-second intervals
|
|
35
|
+
* - Artifact download (reports, videos, logs)
|
|
36
|
+
*
|
|
37
|
+
* Replaces `maestro cloud` with DeviceCloud-specific functionality.
|
|
38
|
+
*/
|
|
26
39
|
class Cloud extends core_1.Command {
|
|
27
40
|
static args = {
|
|
28
41
|
firstFile: core_1.Args.string({
|
|
@@ -40,24 +53,41 @@ class Cloud extends core_1.Command {
|
|
|
40
53
|
static enableJsonFlag = true;
|
|
41
54
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
42
55
|
static flags = constants_1.flags;
|
|
56
|
+
/** Service for device/OS compatibility validation */
|
|
43
57
|
deviceValidationService = new device_validation_service_1.DeviceValidationService();
|
|
58
|
+
/** Service for Moropo test framework integration */
|
|
44
59
|
moropoService = new moropo_service_1.MoropoService();
|
|
60
|
+
/** Service for downloading test reports and artifacts */
|
|
45
61
|
reportDownloadService = new report_download_service_1.ReportDownloadService();
|
|
62
|
+
/** Service for polling test results with 5-second intervals */
|
|
46
63
|
resultsPollingService = new results_polling_service_1.ResultsPollingService();
|
|
64
|
+
/** Service for submitting tests to the API */
|
|
47
65
|
testSubmissionService = new test_submission_service_1.TestSubmissionService();
|
|
66
|
+
/**
|
|
67
|
+
* Check for CLI updates and notify user if outdated
|
|
68
|
+
* Compares current version with latest npm release
|
|
69
|
+
* @returns Promise that resolves when version check is complete
|
|
70
|
+
*/
|
|
48
71
|
versionCheck = async () => {
|
|
49
72
|
const latestVersion = await this.versionService.checkLatestCliVersion();
|
|
50
73
|
if (latestVersion &&
|
|
51
74
|
this.versionService.isOutdated(this.config.version, latestVersion)) {
|
|
52
|
-
this.log(`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`);
|
|
75
|
+
this.log(`\n${styling_1.dividers.light}\n` +
|
|
76
|
+
`${styling_1.colors.warning('⚠')} ${styling_1.colors.bold('Update Available')}\n` +
|
|
77
|
+
styling_1.colors.dim(` A new version of the DeviceCloud CLI is available: `) + styling_1.colors.highlight(latestVersion) + `\n` +
|
|
78
|
+
styling_1.colors.dim(` Run: `) + styling_1.colors.info(`npm install -g @devicecloud.dev/dcd@latest`) + `\n` +
|
|
79
|
+
`${styling_1.dividers.light}\n`);
|
|
58
80
|
}
|
|
59
81
|
};
|
|
82
|
+
/** Service for CLI version checking */
|
|
60
83
|
versionService = new version_service_1.VersionService();
|
|
84
|
+
/**
|
|
85
|
+
* Main command execution entry point
|
|
86
|
+
* Orchestrates the complete test submission and monitoring workflow
|
|
87
|
+
* @returns Promise that resolves when command execution is complete
|
|
88
|
+
* @throws RunFailedError if tests fail
|
|
89
|
+
* @throws Error for infrastructure or configuration errors
|
|
90
|
+
*/
|
|
61
91
|
async run() {
|
|
62
92
|
let output = null;
|
|
63
93
|
// Store debug flag outside try/catch to access it in catch block
|
|
@@ -135,18 +165,18 @@ class Cloud extends core_1.Command {
|
|
|
135
165
|
logger: this.log.bind(this),
|
|
136
166
|
});
|
|
137
167
|
if (retry && retry > 2) {
|
|
138
|
-
this.log("Retries are now free of charge but limited to 2. If
|
|
168
|
+
this.log(styling_1.colors.warning('⚠') + ' ' + styling_1.colors.dim("Retries are now free of charge but limited to 2. If your test is still failing after 2 retries, please ask for help on Discord."));
|
|
139
169
|
flags.retry = 2;
|
|
140
170
|
retry = 2;
|
|
141
171
|
}
|
|
142
172
|
if (runnerType === 'm4') {
|
|
143
|
-
this.log('Note: runnerType m4 is experimental and currently supports iOS only, Android will revert to default.');
|
|
173
|
+
this.log(styling_1.colors.info('ℹ') + ' ' + styling_1.colors.dim('Note: runnerType m4 is experimental and currently supports iOS only, Android will revert to default.'));
|
|
144
174
|
}
|
|
145
175
|
if (runnerType === 'm1') {
|
|
146
|
-
this.log('Note: runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.');
|
|
176
|
+
this.log(styling_1.colors.info('ℹ') + ' ' + styling_1.colors.dim('Note: runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.'));
|
|
147
177
|
}
|
|
148
178
|
if (runnerType === 'gpu1') {
|
|
149
|
-
this.log('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.');
|
|
179
|
+
this.log(styling_1.colors.info('ℹ') + ' ' + styling_1.colors.dim('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.'));
|
|
150
180
|
}
|
|
151
181
|
const { firstFile, secondFile } = args;
|
|
152
182
|
let finalBinaryId = appBinaryId;
|
|
@@ -260,41 +290,44 @@ class Cloud extends core_1.Command {
|
|
|
260
290
|
const hasOverrides = overridesEntries.some(([, overrides]) => Object.keys(overrides).length > 0);
|
|
261
291
|
let overridesLog = '';
|
|
262
292
|
if (hasOverrides) {
|
|
263
|
-
overridesLog = '\n
|
|
293
|
+
overridesLog = '\n\n ' + styling_1.colors.bold('With overrides');
|
|
264
294
|
for (const [flowPath, overrides] of overridesEntries) {
|
|
265
295
|
if (Object.keys(overrides).length > 0) {
|
|
266
296
|
const relativePath = flowPath.replace(process.cwd(), '.');
|
|
267
|
-
overridesLog += `\n
|
|
297
|
+
overridesLog += `\n ${styling_1.colors.dim('→')} ${relativePath}:`;
|
|
268
298
|
for (const [key, value] of Object.entries(overrides)) {
|
|
269
|
-
overridesLog += `\n
|
|
299
|
+
overridesLog += `\n ${styling_1.colors.dim(key + ':')} ${styling_1.colors.highlight(value)}`;
|
|
270
300
|
}
|
|
271
301
|
}
|
|
272
302
|
}
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
this.log(`
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
303
|
+
}
|
|
304
|
+
this.log(`\n${(0, styling_1.sectionHeader)('Submitting new job')}`);
|
|
305
|
+
this.log(` ${styling_1.colors.dim('→ Flow(s):')} ${styling_1.colors.highlight(flowFile)}`);
|
|
306
|
+
this.log(` ${styling_1.colors.dim('→ App:')} ${styling_1.colors.highlight(appBinaryId || finalAppFile)}`);
|
|
307
|
+
if (flagLogs.length > 0) {
|
|
308
|
+
this.log(`\n ${styling_1.colors.bold('With options')}`);
|
|
309
|
+
for (const flagLog of flagLogs) {
|
|
310
|
+
const [key, ...valueParts] = flagLog.split(': ');
|
|
311
|
+
const value = valueParts.join(': ');
|
|
312
|
+
this.log(` ${styling_1.colors.dim('→ ' + key + ':')} ${styling_1.colors.highlight(value)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (hasOverrides) {
|
|
316
|
+
this.log(overridesLog);
|
|
317
|
+
}
|
|
318
|
+
this.log('');
|
|
286
319
|
if (dryRun) {
|
|
287
|
-
this.log('
|
|
288
|
-
this.log('The following tests would have been run:');
|
|
289
|
-
this.log(
|
|
320
|
+
this.log(`\n${styling_1.colors.warning('⚠')} ${styling_1.colors.bold('Dry run mode')} ${styling_1.colors.dim('- no tests were actually triggered')}\n`);
|
|
321
|
+
this.log(styling_1.colors.bold('The following tests would have been run:'));
|
|
322
|
+
this.log(styling_1.dividers.light);
|
|
290
323
|
for (const test of testFileNames) {
|
|
291
|
-
this.log(
|
|
324
|
+
this.log((0, styling_1.listItem)(test));
|
|
292
325
|
}
|
|
293
326
|
if (sequentialFlows.length > 0) {
|
|
294
|
-
this.log('
|
|
295
|
-
this.log(
|
|
327
|
+
this.log(`\n${styling_1.colors.bold('Sequential flows:')}`);
|
|
328
|
+
this.log(styling_1.dividers.short);
|
|
296
329
|
for (const flow of sequentialFlows) {
|
|
297
|
-
this.log(
|
|
330
|
+
this.log((0, styling_1.listItem)(flow));
|
|
298
331
|
}
|
|
299
332
|
}
|
|
300
333
|
this.log('\n');
|
|
@@ -306,7 +339,7 @@ class Cloud extends core_1.Command {
|
|
|
306
339
|
if (debug) {
|
|
307
340
|
this.log(`DEBUG: Uploading binary file: ${finalAppFile}`);
|
|
308
341
|
}
|
|
309
|
-
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
342
|
+
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck, !json, debug);
|
|
310
343
|
finalBinaryId = binaryId;
|
|
311
344
|
if (debug) {
|
|
312
345
|
this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
|
|
@@ -356,17 +389,18 @@ class Cloud extends core_1.Command {
|
|
|
356
389
|
}
|
|
357
390
|
if (!results?.length)
|
|
358
391
|
(0, errors_1.error)('No tests created: ' + message);
|
|
359
|
-
this.log(message);
|
|
360
|
-
|
|
392
|
+
this.log(styling_1.colors.success('✓') + ' ' + styling_1.colors.dim(message));
|
|
393
|
+
const testNames = results
|
|
361
394
|
.map((r) => r.test_file_name)
|
|
362
395
|
.sort((a, b) => a.localeCompare(b))
|
|
363
|
-
.join(', ')
|
|
364
|
-
this.log(
|
|
365
|
-
const url =
|
|
366
|
-
this.log(
|
|
367
|
-
this.log(
|
|
368
|
-
this.log(
|
|
369
|
-
this.log(
|
|
396
|
+
.join(styling_1.colors.dim(', '));
|
|
397
|
+
this.log(`\n${styling_1.colors.bold(`Created ${results.length} test${results.length === 1 ? '' : 's'}:`)} ${testNames}\n`);
|
|
398
|
+
const url = (0, styling_1.getConsoleUrl)(apiUrl, results[0].test_upload_id, results[0].id);
|
|
399
|
+
this.log(styling_1.colors.bold('Run triggered') + styling_1.colors.dim(', you can access the results at:'));
|
|
400
|
+
this.log((0, styling_1.formatUrl)(url));
|
|
401
|
+
this.log(``);
|
|
402
|
+
this.log(styling_1.colors.dim('Your upload ID is: ') + (0, styling_1.formatId)(results[0].test_upload_id));
|
|
403
|
+
this.log(styling_1.colors.dim('Poll upload status using: ') + styling_1.colors.info(`dcd status --api-key ... --upload-id ${results[0].test_upload_id}`));
|
|
370
404
|
if (async) {
|
|
371
405
|
if (debug) {
|
|
372
406
|
this.log(`DEBUG: Async flag is set, not waiting for results`);
|
|
@@ -387,7 +421,7 @@ class Cloud extends core_1.Command {
|
|
|
387
421
|
if (json) {
|
|
388
422
|
return jsonOutput;
|
|
389
423
|
}
|
|
390
|
-
this.log('Not waiting for results as async flag is set to true');
|
|
424
|
+
this.log(`\n${styling_1.colors.info('ℹ')} ${styling_1.colors.dim('Not waiting for results as async flag is set to true')}\n`);
|
|
391
425
|
return;
|
|
392
426
|
}
|
|
393
427
|
// Poll for results until completion
|
|
@@ -31,6 +31,5 @@ export default class Status extends Command {
|
|
|
31
31
|
'upload-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
32
32
|
};
|
|
33
33
|
run(): Promise<StatusResponse | void>;
|
|
34
|
-
private getStatusSymbol;
|
|
35
34
|
}
|
|
36
35
|
export {};
|
package/dist/commands/status.js
CHANGED
|
@@ -5,6 +5,7 @@ const constants_1 = require("../constants");
|
|
|
5
5
|
const api_gateway_1 = require("../gateways/api-gateway");
|
|
6
6
|
const methods_1 = require("../methods");
|
|
7
7
|
const connectivity_1 = require("../utils/connectivity");
|
|
8
|
+
const styling_1 = require("../utils/styling");
|
|
8
9
|
class Status extends core_1.Command {
|
|
9
10
|
static description = 'Get the status of an upload by name or upload ID';
|
|
10
11
|
static enableJsonFlag = true;
|
|
@@ -98,32 +99,27 @@ class Status extends core_1.Command {
|
|
|
98
99
|
if (json) {
|
|
99
100
|
return status;
|
|
100
101
|
}
|
|
101
|
-
this.log('
|
|
102
|
-
this.log('═'.repeat(80));
|
|
102
|
+
this.log((0, styling_1.sectionHeader)('Upload Status'));
|
|
103
103
|
// Display overall status
|
|
104
|
-
|
|
105
|
-
this.log(`${overallSymbol} Status: ${status.status}`);
|
|
104
|
+
this.log(` ${(0, styling_1.formatStatus)(status.status)}`);
|
|
106
105
|
if (status.uploadId) {
|
|
107
|
-
this.log(
|
|
106
|
+
this.log(` ${styling_1.colors.dim('Upload ID:')} ${(0, styling_1.formatId)(status.uploadId)}`);
|
|
108
107
|
}
|
|
109
108
|
if (status.appBinaryId) {
|
|
110
|
-
this.log(
|
|
109
|
+
this.log(` ${styling_1.colors.dim('Binary ID:')} ${(0, styling_1.formatId)(status.appBinaryId)}`);
|
|
111
110
|
}
|
|
112
111
|
if (status.consoleUrl) {
|
|
113
|
-
this.log(
|
|
112
|
+
this.log(` ${styling_1.colors.dim('Console:')} ${(0, styling_1.formatUrl)(status.consoleUrl)}`);
|
|
114
113
|
}
|
|
115
114
|
if (status.tests.length > 0) {
|
|
116
|
-
this.log('
|
|
117
|
-
this.log('─'.repeat(80));
|
|
115
|
+
this.log((0, styling_1.sectionHeader)('Test Results'));
|
|
118
116
|
for (const item of status.tests) {
|
|
119
|
-
|
|
120
|
-
this.log(`${symbol} ${item.name}`);
|
|
121
|
-
this.log(` Status: ${item.status}`);
|
|
117
|
+
this.log(` ${(0, styling_1.formatStatus)(item.status)} ${styling_1.colors.bold(item.name)}`);
|
|
122
118
|
if (item.status === 'FAILED' && item.failReason) {
|
|
123
|
-
this.log(`
|
|
119
|
+
this.log(` ${styling_1.colors.error('Fail reason:')} ${item.failReason}`);
|
|
124
120
|
}
|
|
125
121
|
if (item.durationSeconds) {
|
|
126
|
-
this.log(`
|
|
122
|
+
this.log(` ${styling_1.colors.dim('Duration:')} ${(0, methods_1.formatDurationSeconds)(item.durationSeconds)}`);
|
|
127
123
|
}
|
|
128
124
|
this.log('');
|
|
129
125
|
}
|
|
@@ -133,24 +129,5 @@ class Status extends core_1.Command {
|
|
|
133
129
|
this.error(`Failed to get status: ${error.message}`);
|
|
134
130
|
}
|
|
135
131
|
}
|
|
136
|
-
getStatusSymbol(status) {
|
|
137
|
-
switch (status) {
|
|
138
|
-
case 'PASSED': {
|
|
139
|
-
return '✓';
|
|
140
|
-
}
|
|
141
|
-
case 'FAILED': {
|
|
142
|
-
return '✗';
|
|
143
|
-
}
|
|
144
|
-
case 'CANCELLED': {
|
|
145
|
-
return '⊘';
|
|
146
|
-
}
|
|
147
|
-
case 'PENDING': {
|
|
148
|
-
return '⋯';
|
|
149
|
-
}
|
|
150
|
-
default: {
|
|
151
|
-
return '?';
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
132
|
}
|
|
156
133
|
exports.default = Status;
|
|
@@ -9,6 +9,7 @@ export default class Upload extends Command {
|
|
|
9
9
|
static flags: {
|
|
10
10
|
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
11
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
};
|
|
14
15
|
run(): Promise<{
|
package/dist/commands/upload.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
5
|
const methods_1 = require("../methods");
|
|
6
|
+
const styling_1 = require("../utils/styling");
|
|
6
7
|
class Upload extends core_1.Command {
|
|
7
8
|
static args = {
|
|
8
9
|
appFile: core_1.Args.string({
|
|
@@ -20,13 +21,14 @@ class Upload extends core_1.Command {
|
|
|
20
21
|
static flags = {
|
|
21
22
|
apiKey: constants_1.flags.apiKey,
|
|
22
23
|
apiUrl: constants_1.flags.apiUrl,
|
|
24
|
+
debug: constants_1.flags.debug,
|
|
23
25
|
'ignore-sha-check': constants_1.flags['ignore-sha-check'],
|
|
24
26
|
};
|
|
25
27
|
async run() {
|
|
26
28
|
try {
|
|
27
29
|
const { args, flags } = await this.parse(Upload);
|
|
28
30
|
const { appFile } = args;
|
|
29
|
-
const { apiKey: apiKeyFlag, apiUrl, 'ignore-sha-check': ignoreShaCheck, json, } = flags;
|
|
31
|
+
const { apiKey: apiKeyFlag, apiUrl, debug, 'ignore-sha-check': ignoreShaCheck, json, } = flags;
|
|
30
32
|
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
31
33
|
if (!apiKey) {
|
|
32
34
|
throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
|
|
@@ -37,17 +39,17 @@ class Upload extends core_1.Command {
|
|
|
37
39
|
if (appFile.endsWith('.zip')) {
|
|
38
40
|
await (0, methods_1.verifyAppZip)(appFile);
|
|
39
41
|
}
|
|
40
|
-
this.log(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const appBinaryId = await (0, methods_1.uploadBinary)(appFile, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
42
|
+
this.log((0, styling_1.sectionHeader)('Uploading app binary'));
|
|
43
|
+
this.log(` ${styling_1.colors.dim('→ File:')} ${styling_1.colors.highlight(appFile)}`);
|
|
44
|
+
this.log('');
|
|
45
|
+
const appBinaryId = await (0, methods_1.uploadBinary)(appFile, apiUrl, apiKey, ignoreShaCheck, !json, debug);
|
|
45
46
|
if (json) {
|
|
46
47
|
return { appBinaryId };
|
|
47
48
|
}
|
|
48
|
-
this.log(`\
|
|
49
|
-
this.log(`
|
|
50
|
-
this.log(
|
|
49
|
+
this.log(`\n${styling_1.colors.success('✓')} ${styling_1.colors.bold('Upload complete')}`);
|
|
50
|
+
this.log(` ${styling_1.colors.dim('Binary ID:')} ${(0, styling_1.formatId)(appBinaryId)}\n`);
|
|
51
|
+
this.log(styling_1.colors.dim('You can use this Binary ID in subsequent test runs with:'));
|
|
52
|
+
this.log(styling_1.colors.info(`dcd cloud --app-binary-id ${appBinaryId} path/to/flow.yaml\n`));
|
|
51
53
|
}
|
|
52
54
|
catch (error) {
|
|
53
55
|
this.error(error, { exit: 1 });
|
|
@@ -44,7 +44,7 @@ exports.executionFlags = {
|
|
|
44
44
|
}),
|
|
45
45
|
'runner-type': core_1.Flags.string({
|
|
46
46
|
default: 'default',
|
|
47
|
-
description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information.
|
|
48
|
-
options: ['default', 'm4', 'm1', 'gpu1'],
|
|
47
|
+
description: '[experimental] The type of runner to use - note: anything other than default or cpu1 will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information.',
|
|
48
|
+
options: ['default', 'm4', 'm1', 'gpu1', 'cpu1'],
|
|
49
49
|
}),
|
|
50
50
|
};
|
|
@@ -14,14 +14,14 @@ export declare const ApiGateway: {
|
|
|
14
14
|
downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
|
|
15
15
|
finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
|
|
16
16
|
getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
|
|
17
|
-
id: string;
|
|
18
17
|
message: string;
|
|
19
18
|
path: string;
|
|
20
19
|
token: string;
|
|
20
|
+
id: string;
|
|
21
21
|
}>;
|
|
22
22
|
getResultsForUpload(baseUrl: string, apiKey: string, uploadId: string): Promise<{
|
|
23
|
-
results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
|
|
24
23
|
statusCode?: number;
|
|
24
|
+
results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
|
|
25
25
|
}>;
|
|
26
26
|
getUploadStatus(baseUrl: string, apiKey: string, options: {
|
|
27
27
|
name?: string;
|
|
@@ -7,5 +7,5 @@ export declare class SupabaseGateway {
|
|
|
7
7
|
SUPABASE_PUBLIC_KEY: string;
|
|
8
8
|
SUPABASE_URL: string;
|
|
9
9
|
};
|
|
10
|
-
static uploadToSignedUrl(env: 'dev' | 'prod', path: string, token: string, file: File): Promise<void>;
|
|
10
|
+
static uploadToSignedUrl(env: 'dev' | 'prod', path: string, token: string, file: File, debug?: boolean): Promise<void>;
|
|
11
11
|
}
|
|
@@ -16,16 +16,58 @@ class SupabaseGateway {
|
|
|
16
16
|
static getSupabaseKeys(env) {
|
|
17
17
|
return this.SB[env];
|
|
18
18
|
}
|
|
19
|
-
static async uploadToSignedUrl(env, path, token, file) {
|
|
19
|
+
static async uploadToSignedUrl(env, path, token, file, debug = false) {
|
|
20
20
|
const { SUPABASE_PUBLIC_KEY, SUPABASE_URL } = this.getSupabaseKeys(env);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
if (debug) {
|
|
22
|
+
console.log(`[DEBUG] Supabase upload starting...`);
|
|
23
|
+
console.log(`[DEBUG] Supabase URL: ${SUPABASE_URL}`);
|
|
24
|
+
console.log(`[DEBUG] Upload path: ${path}`);
|
|
25
|
+
console.log(`[DEBUG] File name: ${file.name}`);
|
|
26
|
+
console.log(`[DEBUG] File type: ${file.type}`);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY);
|
|
30
|
+
const uploadToUrl = await supabase.storage
|
|
31
|
+
.from('organizations')
|
|
32
|
+
.uploadToSignedUrl(path, token, file);
|
|
33
|
+
if (uploadToUrl.error) {
|
|
34
|
+
const error = uploadToUrl.error;
|
|
35
|
+
const errorMessage = typeof error === 'string' ? error : error?.message || 'Upload failed';
|
|
36
|
+
if (debug) {
|
|
37
|
+
console.error(`[DEBUG] Supabase upload error:`, error);
|
|
38
|
+
if (typeof error === 'object' && 'statusCode' in error) {
|
|
39
|
+
console.error(`[DEBUG] HTTP Status Code: ${error.statusCode}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw new Error(errorMessage);
|
|
43
|
+
}
|
|
44
|
+
if (debug) {
|
|
45
|
+
console.log(`[DEBUG] Supabase upload successful`);
|
|
46
|
+
console.log(`[DEBUG] Upload result path: ${uploadToUrl.data?.path || 'N/A'}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (debug) {
|
|
51
|
+
console.error(`[DEBUG] Supabase upload exception:`, error);
|
|
52
|
+
console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`);
|
|
53
|
+
console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
// Check for common network errors
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
if (error.message.includes('ECONNREFUSED')) {
|
|
57
|
+
console.error(`[DEBUG] Network error: Connection refused - check internet connectivity`);
|
|
58
|
+
}
|
|
59
|
+
else if (error.message.includes('ETIMEDOUT')) {
|
|
60
|
+
console.error(`[DEBUG] Network error: Connection timeout - check internet connectivity or try again`);
|
|
61
|
+
}
|
|
62
|
+
else if (error.message.includes('ENOTFOUND')) {
|
|
63
|
+
console.error(`[DEBUG] Network error: DNS lookup failed - check internet connectivity`);
|
|
64
|
+
}
|
|
65
|
+
else if (error.message.includes('ECONNRESET')) {
|
|
66
|
+
console.error(`[DEBUG] Network error: Connection reset - network unstable or interrupted`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
29
71
|
}
|
|
30
72
|
}
|
|
31
73
|
}
|
package/dist/methods.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const toBuffer: (archive: archiver.Archiver) => Promise<Buffer<Ar
|
|
|
3
3
|
export declare const compressFolderToBlob: (sourceDir: string) => Promise<Blob>;
|
|
4
4
|
export declare const compressFilesFromRelativePath: (basePath: string, files: string[], commonRoot: string) => Promise<Buffer<ArrayBuffer>>;
|
|
5
5
|
export declare const verifyAppZip: (zipPath: string) => Promise<void>;
|
|
6
|
-
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string>;
|
|
6
|
+
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean, debug?: boolean) => Promise<string>;
|
|
7
7
|
/**
|
|
8
8
|
* Writes JSON data to a file with error handling
|
|
9
9
|
* @param filePath - Path to the output JSON file
|