@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.
@@ -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
  }
@@ -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
- A new version of the devicecloud.dev CLI is available: ${latestVersion}
55
- Run 'npm install -g @devicecloud.dev/dcd@latest' to update to the latest version
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 you're test is still failing after 2 retries, please ask for help on Discord.");
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 With overrides';
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 → ${relativePath}:`;
297
+ overridesLog += `\n ${styling_1.colors.dim('')} ${relativePath}:`;
268
298
  for (const [key, value] of Object.entries(overrides)) {
269
- overridesLog += `\n ${key}: ${value}`;
299
+ overridesLog += `\n ${styling_1.colors.dim(key + ':')} ${styling_1.colors.highlight(value)}`;
270
300
  }
271
301
  }
272
302
  }
273
- overridesLog += '\n';
274
- }
275
- this.log(`
276
-
277
- Submitting new job
278
- → Flow(s): ${flowFile}
279
- App: ${appBinaryId || finalAppFile}
280
-
281
- With options
282
- → ${flagLogs.join(`
283
- → `)}${overridesLog}
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('\nDry run mode - no tests were actually triggered\n');
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(`• ${test}`);
324
+ this.log((0, styling_1.listItem)(test));
292
325
  }
293
326
  if (sequentialFlows.length > 0) {
294
- this.log('\nSequential flows:');
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(`• ${flow}`);
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
- this.log(`\nCreated ${results.length} tests: ${results
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(', ')}\n`);
364
- this.log('Run triggered, you can access the results at:');
365
- const url = `https://console.devicecloud.dev/results?upload=${results[0].test_upload_id}&result=${results[0].id}`;
366
- this.log(url);
367
- this.log(`\n`);
368
- this.log(`Your upload ID is: ${results[0].test_upload_id}`);
369
- this.log(`Poll upload status using: dcd status --api-key ... --upload-id ${results[0].test_upload_id}`);
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 {};
@@ -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('\n📊 Upload Status');
102
- this.log('═'.repeat(80));
102
+ this.log((0, styling_1.sectionHeader)('Upload Status'));
103
103
  // Display overall status
104
- const overallSymbol = this.getStatusSymbol(status.status);
105
- this.log(`${overallSymbol} Status: ${status.status}`);
104
+ this.log(` ${(0, styling_1.formatStatus)(status.status)}`);
106
105
  if (status.uploadId) {
107
- this.log(`🆔 Upload ID: ${status.uploadId}`);
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(`📱 Binary ID: ${status.appBinaryId}`);
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(`🔗 Console: ${status.consoleUrl}`);
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('\n📋 Test Results');
117
- this.log('─'.repeat(80));
115
+ this.log((0, styling_1.sectionHeader)('Test Results'));
118
116
  for (const item of status.tests) {
119
- const symbol = this.getStatusSymbol(item.status);
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(` Fail reason: ${item.failReason}`);
119
+ this.log(` ${styling_1.colors.error('Fail reason:')} ${item.failReason}`);
124
120
  }
125
121
  if (item.durationSeconds) {
126
- this.log(` Duration: ${(0, methods_1.formatDurationSeconds)(item.durationSeconds)}`);
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<{
@@ -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
- Uploading app binary
42
- → File: ${appFile}
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(`\nUpload complete. Binary ID: ${appBinaryId}\n`);
49
- this.log(`You can use this Binary ID in subsequent test runs with:`);
50
- this.log(`dcd cloud --app-binary-id ${appBinaryId} path/to/flow.yaml\n`);
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. gpu1 is Android-only and requires contacting support to enable, otherwise reverts to default.',
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
- const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY);
22
- const uploadToUrl = await supabase.storage
23
- .from('organizations')
24
- .uploadToSignedUrl(path, token, file);
25
- if (uploadToUrl.error) {
26
- const error = uploadToUrl.error;
27
- const errorMessage = typeof error === 'string' ? error : error?.message || 'Upload failed';
28
- throw new Error(errorMessage);
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