@devicecloud.dev/dcd 5.0.0-beta.0 → 5.0.0-beta.2

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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/commands/artifacts.d.ts +28 -28
  4. package/dist/commands/artifacts.js +20 -23
  5. package/dist/commands/cloud.d.ts +57 -57
  6. package/dist/commands/cloud.js +224 -192
  7. package/dist/commands/list.d.ts +22 -22
  8. package/dist/commands/list.js +43 -40
  9. package/dist/commands/live.js +134 -127
  10. package/dist/commands/login.d.ts +11 -11
  11. package/dist/commands/login.js +46 -44
  12. package/dist/commands/logout.js +16 -18
  13. package/dist/commands/status.d.ts +11 -11
  14. package/dist/commands/status.js +53 -44
  15. package/dist/commands/switch-org.d.ts +7 -7
  16. package/dist/commands/switch-org.js +19 -21
  17. package/dist/commands/upgrade.js +41 -33
  18. package/dist/commands/upload.d.ts +10 -10
  19. package/dist/commands/upload.js +42 -43
  20. package/dist/commands/whoami.js +17 -20
  21. package/dist/config/environments.js +6 -12
  22. package/dist/config/flags/api.flags.js +1 -4
  23. package/dist/config/flags/binary.flags.js +1 -4
  24. package/dist/config/flags/device.flags.js +6 -9
  25. package/dist/config/flags/environment.flags.js +1 -4
  26. package/dist/config/flags/execution.flags.js +1 -4
  27. package/dist/config/flags/github.flags.js +1 -4
  28. package/dist/config/flags/output.flags.js +1 -4
  29. package/dist/constants.js +15 -18
  30. package/dist/gateways/api-gateway.d.ts +31 -6
  31. package/dist/gateways/api-gateway.js +70 -16
  32. package/dist/gateways/cli-auth-gateway.d.ts +1 -1
  33. package/dist/gateways/cli-auth-gateway.js +3 -6
  34. package/dist/gateways/realtime-gateway.d.ts +32 -0
  35. package/dist/gateways/realtime-gateway.js +103 -0
  36. package/dist/gateways/supabase-gateway.d.ts +1 -1
  37. package/dist/gateways/supabase-gateway.js +10 -14
  38. package/dist/index.js +41 -38
  39. package/dist/mcp/context.d.ts +33 -0
  40. package/dist/mcp/context.js +33 -0
  41. package/dist/mcp/helpers.d.ts +16 -0
  42. package/dist/mcp/helpers.js +34 -0
  43. package/dist/mcp/index.d.ts +2 -0
  44. package/dist/mcp/index.js +24 -0
  45. package/dist/mcp/server.d.ts +7 -0
  46. package/dist/mcp/server.js +27 -0
  47. package/dist/mcp/tools/download-artifacts.d.ts +11 -0
  48. package/dist/mcp/tools/download-artifacts.js +84 -0
  49. package/dist/mcp/tools/get-status.d.ts +7 -0
  50. package/dist/mcp/tools/get-status.js +39 -0
  51. package/dist/mcp/tools/list-devices.d.ts +7 -0
  52. package/dist/mcp/tools/list-devices.js +27 -0
  53. package/dist/mcp/tools/list-runs.d.ts +3 -0
  54. package/dist/mcp/tools/list-runs.js +60 -0
  55. package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
  56. package/dist/mcp/tools/run-cloud-test.js +233 -0
  57. package/dist/methods.d.ts +32 -1
  58. package/dist/methods.js +133 -79
  59. package/dist/services/device-validation.service.d.ts +1 -1
  60. package/dist/services/device-validation.service.js +1 -5
  61. package/dist/services/execution-plan.service.js +14 -17
  62. package/dist/services/execution-plan.utils.js +15 -23
  63. package/dist/services/flow-paths.d.ts +17 -0
  64. package/dist/services/flow-paths.js +52 -0
  65. package/dist/services/metadata-extractor.service.js +22 -25
  66. package/dist/services/moropo.service.js +18 -20
  67. package/dist/services/report-download.service.d.ts +1 -1
  68. package/dist/services/report-download.service.js +5 -9
  69. package/dist/services/results-polling.service.d.ts +18 -3
  70. package/dist/services/results-polling.service.js +211 -108
  71. package/dist/services/telemetry.service.d.ts +10 -1
  72. package/dist/services/telemetry.service.js +40 -18
  73. package/dist/services/test-submission.service.d.ts +21 -4
  74. package/dist/services/test-submission.service.js +51 -34
  75. package/dist/services/version.service.d.ts +30 -7
  76. package/dist/services/version.service.js +88 -32
  77. package/dist/types/domain/auth.types.d.ts +8 -0
  78. package/dist/types/domain/auth.types.js +1 -2
  79. package/dist/types/domain/device.types.js +8 -11
  80. package/dist/types/domain/live.types.js +1 -2
  81. package/dist/types/generated/schema.types.js +1 -2
  82. package/dist/types/index.d.ts +2 -2
  83. package/dist/types/index.js +2 -18
  84. package/dist/types.js +1 -2
  85. package/dist/utils/auth.d.ts +1 -1
  86. package/dist/utils/auth.js +27 -28
  87. package/dist/utils/ci.d.ts +12 -0
  88. package/dist/utils/ci.js +39 -0
  89. package/dist/utils/cli.d.ts +16 -2
  90. package/dist/utils/cli.js +57 -29
  91. package/dist/utils/compatibility.d.ts +1 -1
  92. package/dist/utils/compatibility.js +5 -7
  93. package/dist/utils/config-store.js +33 -43
  94. package/dist/utils/connectivity.js +1 -4
  95. package/dist/utils/expo.js +15 -21
  96. package/dist/utils/orgs.js +8 -12
  97. package/dist/utils/paths.js +2 -5
  98. package/dist/utils/progress.d.ts +3 -0
  99. package/dist/utils/progress.js +47 -8
  100. package/dist/utils/styling.d.ts +35 -37
  101. package/dist/utils/styling.js +52 -86
  102. package/dist/utils/ui.d.ts +41 -0
  103. package/dist/utils/ui.js +95 -0
  104. package/package.json +27 -24
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.upgradeCommand = void 0;
4
1
  /**
5
2
  * `dcd upgrade` — in-place replace of the standalone binary.
6
3
  *
@@ -10,14 +7,15 @@ exports.upgradeCommand = void 0;
10
7
  * against the published SHA256SUMS, then atomically renames over the running
11
8
  * executable (the old inode stays alive until the process exits).
12
9
  */
13
- const node_crypto_1 = require("node:crypto");
14
- const node_fs_1 = require("node:fs");
15
- const node_stream_1 = require("node:stream");
16
- const promises_1 = require("node:stream/promises");
17
- const citty_1 = require("citty");
18
- const version_service_1 = require("../services/version.service");
19
- const cli_1 = require("../utils/cli");
20
- const styling_1 = require("../utils/styling");
10
+ import { createHash } from 'node:crypto';
11
+ import { chmodSync, createReadStream, createWriteStream, renameSync, unlinkSync, } from 'node:fs';
12
+ import { Readable } from 'node:stream';
13
+ import { pipeline } from 'node:stream/promises';
14
+ import { defineCommand } from 'citty';
15
+ import { VersionService } from '../services/version.service.js';
16
+ import { CliError, getCliVersion, getInstallMethod, logger, } from '../utils/cli.js';
17
+ import { colors } from '../utils/styling.js';
18
+ import { ui } from '../utils/ui.js';
21
19
  const DEFAULT_DOWNLOAD_BASE = 'https://get.devicecloud.dev';
22
20
  // Maps `${process.platform}-${process.arch}` to the GitHub Release asset filename
23
21
  // produced by scripts/build-binaries.mjs. Keys must stay in sync with that script.
@@ -28,41 +26,51 @@ const ASSET_BY_PLATFORM = {
28
26
  'linux-x64': 'dcd-linux-x64',
29
27
  'win32-x64': 'dcd-windows-x64.exe',
30
28
  };
31
- exports.upgradeCommand = (0, citty_1.defineCommand)({
29
+ export const upgradeCommand = defineCommand({
32
30
  meta: {
33
31
  name: 'upgrade',
34
32
  description: 'Upgrade dcd in place to the latest released version',
35
33
  },
36
34
  async run() {
37
- if ((0, cli_1.getInstallMethod)() !== 'binary') {
38
- throw new cli_1.CliError('`dcd upgrade` only applies to the standalone binary install. ' +
35
+ if (getInstallMethod() !== 'binary') {
36
+ throw new CliError('`dcd upgrade` only applies to the standalone binary install. ' +
39
37
  'Run: npm install -g @devicecloud.dev/dcd@latest');
40
38
  }
41
- const current = (0, cli_1.getCliVersion)();
42
- const versionService = new version_service_1.VersionService();
43
- const latest = await versionService.checkLatestCliVersion();
44
- if (!latest) {
45
- throw new cli_1.CliError('Could not reach the update manifest. Check your network connection and try again.');
39
+ const current = getCliVersion();
40
+ const versionService = new VersionService();
41
+ const result = await versionService.checkLatestCliVersion(current);
42
+ if (!result.ok) {
43
+ throw new CliError(`Could not reach the update manifest (${result.error}). Check your network connection and try again.`);
46
44
  }
45
+ // Reachable, but nothing published on this channel yet (e.g. a stable
46
+ // install while only betas exist). Not an error — just nothing to do.
47
+ if (result.version === null) {
48
+ logger.log(ui.info(`No newer release available on the ${result.channel} channel.`));
49
+ return;
50
+ }
51
+ const latest = result.version;
47
52
  if (!versionService.isOutdated(current, latest)) {
48
- cli_1.logger.log(`${styling_1.symbols.success} Already on the latest version (${styling_1.colors.highlight(current)}).`);
53
+ logger.log(ui.success(`Already on the latest version (${colors.highlight(current)})`));
49
54
  return;
50
55
  }
51
56
  const platformKey = `${process.platform}-${process.arch}`;
52
57
  const asset = ASSET_BY_PLATFORM[platformKey];
53
58
  if (!asset) {
54
- throw new cli_1.CliError(`No binary published for ${platformKey}. Supported platforms: ${Object.keys(ASSET_BY_PLATFORM).join(', ')}`);
59
+ throw new CliError(`No binary published for ${platformKey}. Supported platforms: ${Object.keys(ASSET_BY_PLATFORM).join(', ')}`);
55
60
  }
56
61
  if (process.platform === 'win32') {
57
62
  // Windows can't replace a running .exe; defer to a re-run of the installer.
58
63
  const base = process.env.DCD_DOWNLOAD_BASE ?? DEFAULT_DOWNLOAD_BASE;
59
- throw new cli_1.CliError(`Automatic upgrade on Windows is not yet supported. Re-run the installer:\n irm ${base}/install.ps1 | iex`);
64
+ // Prerelease users need the beta channel opt-in or the installer resolves
65
+ // the (currently non-existent) stable release.
66
+ const betaHint = result.channel === 'beta' ? '$env:DCD_BETA=1; ' : '';
67
+ throw new CliError(`Automatic upgrade on Windows is not yet supported. Re-run the installer:\n ${betaHint}irm ${base}/install.ps1 | iex`);
60
68
  }
61
69
  const base = process.env.DCD_DOWNLOAD_BASE ?? DEFAULT_DOWNLOAD_BASE;
62
70
  const binaryUrl = `${base}/download/${latest}/${asset}`;
63
71
  const sumsUrl = `${base}/download/${latest}/SHA256SUMS`;
64
- cli_1.logger.log(`${styling_1.symbols.info} Upgrading ${styling_1.colors.highlight(current)} → ${styling_1.colors.highlight(latest)}`);
65
- cli_1.logger.log(styling_1.colors.dim(` ${binaryUrl}`));
72
+ logger.log(ui.info(`Upgrading ${colors.highlight(current)} → ${colors.highlight(latest)}`));
73
+ logger.log(ui.note(` ${binaryUrl}`));
66
74
  const execPath = process.execPath;
67
75
  const tmpPath = `${execPath}.new`;
68
76
  try {
@@ -72,16 +80,16 @@ exports.upgradeCommand = (0, citty_1.defineCommand)({
72
80
  if (expected !== actual) {
73
81
  throw new Error(`Checksum mismatch for ${asset}: expected ${expected}, got ${actual}`);
74
82
  }
75
- (0, node_fs_1.chmodSync)(tmpPath, 0o755);
83
+ chmodSync(tmpPath, 0o755);
76
84
  // rename is atomic on the same filesystem; on POSIX the running process
77
85
  // keeps the old inode open until exit, so this is safe to do mid-run.
78
- (0, node_fs_1.renameSync)(tmpPath, execPath);
86
+ renameSync(tmpPath, execPath);
79
87
  }
80
88
  catch (e) {
81
89
  safeUnlink(tmpPath);
82
- throw new cli_1.CliError(`Upgrade failed: ${e.message}. The existing binary at ${execPath} was not modified.`);
90
+ throw new CliError(`Upgrade failed: ${e.message}. The existing binary at ${execPath} was not modified.`);
83
91
  }
84
- cli_1.logger.log(`${styling_1.symbols.success} Upgraded to ${styling_1.colors.highlight(latest)}`);
92
+ logger.log(ui.success(`Upgraded to ${colors.highlight(latest)}`));
85
93
  },
86
94
  });
87
95
  async function downloadToFile(url, dest) {
@@ -90,7 +98,7 @@ async function downloadToFile(url, dest) {
90
98
  throw new Error(`HTTP ${res.status} fetching ${url}`);
91
99
  }
92
100
  // Node 22 exposes Readable.fromWeb for piping a WHATWG ReadableStream.
93
- await (0, promises_1.pipeline)(node_stream_1.Readable.fromWeb(res.body), (0, node_fs_1.createWriteStream)(dest));
101
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
94
102
  }
95
103
  async function fetchExpectedChecksum(sumsUrl, asset) {
96
104
  const res = await fetch(sumsUrl, { redirect: 'follow' });
@@ -105,18 +113,18 @@ async function fetchExpectedChecksum(sumsUrl, asset) {
105
113
  throw new Error(`SHA256SUMS has no entry for ${asset}`);
106
114
  }
107
115
  async function sha256File(path) {
108
- const hash = (0, node_crypto_1.createHash)('sha256');
109
- for await (const chunk of (0, node_fs_1.createReadStream)(path)) {
116
+ const hash = createHash('sha256');
117
+ for await (const chunk of createReadStream(path)) {
110
118
  hash.update(chunk);
111
119
  }
112
120
  return hash.digest('hex');
113
121
  }
114
122
  function safeUnlink(path) {
115
123
  try {
116
- (0, node_fs_1.unlinkSync)(path);
124
+ unlinkSync(path);
117
125
  }
118
126
  catch {
119
127
  // Best-effort cleanup; failure here would only leave a .new file.
120
128
  }
121
129
  }
122
- exports.default = exports.upgradeCommand;
130
+ export default upgradeCommand;
@@ -1,32 +1,32 @@
1
1
  export declare const uploadCommand: import("citty").CommandDef<{
2
- 'app-url': {
2
+ readonly 'app-url': {
3
3
  readonly type: "string";
4
4
  readonly description: "Signed URL to an Expo iOS build (.tar.gz). The archive is downloaded and extracted automatically. Expo signed URLs expire after ~1 hour.";
5
5
  };
6
- 'ignore-sha-check': {
6
+ readonly 'ignore-sha-check': {
7
7
  readonly type: "boolean";
8
8
  readonly description: "Ignore the sha hash check and upload the binary regardless of whether it already exists (not recommended)";
9
9
  };
10
- debug: {
10
+ readonly debug: {
11
11
  readonly type: "boolean";
12
12
  readonly default: false;
13
13
  readonly description: "Enable detailed debug logging for troubleshooting issues";
14
14
  };
15
- json: {
15
+ readonly json: {
16
16
  readonly type: "boolean";
17
17
  readonly description: "Output results in JSON format. Exit codes: 0 on success, 2 if the test run fails, 1 on CLI/infrastructure errors";
18
18
  };
19
- appFile: {
20
- type: "positional";
21
- required: false;
22
- description: string;
19
+ readonly appFile: {
20
+ readonly type: "positional";
21
+ readonly required: false;
22
+ readonly description: "The binary file or Expo signed URL to upload (e.g. test.apk, test.app, test.zip, build.tar.gz, or https://expo.dev/...)";
23
23
  };
24
- 'api-key': {
24
+ readonly 'api-key': {
25
25
  readonly type: "string";
26
26
  readonly alias: ["apiKey"];
27
27
  readonly description: "API key for devicecloud.dev (find this in the console UI). You can also set the DEVICE_CLOUD_API_KEY environment variable.";
28
28
  };
29
- 'api-url': {
29
+ readonly 'api-url': {
30
30
  readonly type: "string";
31
31
  readonly alias: ["apiURL", "apiUrl"];
32
32
  readonly description: "API base URL (defaults to the URL stored by `dcd login`, else prod)";
@@ -1,28 +1,26 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadCommand = void 0;
4
- const citty_1 = require("citty");
5
- const promises_1 = require("node:fs/promises");
6
- const api_flags_1 = require("../config/flags/api.flags");
7
- const binary_flags_1 = require("../config/flags/binary.flags");
8
- const output_flags_1 = require("../config/flags/output.flags");
9
- const methods_1 = require("../methods");
10
- const auth_1 = require("../utils/auth");
11
- const cli_1 = require("../utils/cli");
12
- const config_store_1 = require("../utils/config-store");
13
- const expo_1 = require("../utils/expo");
14
- const styling_1 = require("../utils/styling");
15
- exports.uploadCommand = (0, citty_1.defineCommand)({
1
+ import { defineCommand } from 'citty';
2
+ import { rm } from 'node:fs/promises';
3
+ import { apiFlags } from '../config/flags/api.flags.js';
4
+ import { binaryFlags } from '../config/flags/binary.flags.js';
5
+ import { outputFlags } from '../config/flags/output.flags.js';
6
+ import { uploadBinary, verifyAppZip } from '../methods.js';
7
+ import { resolveAuth } from '../utils/auth.js';
8
+ import { CliError, logger } from '../utils/cli.js';
9
+ import { resolveApiUrl } from '../utils/config-store.js';
10
+ import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo.js';
11
+ import { colors, formatId } from '../utils/styling.js';
12
+ import { ui } from '../utils/ui.js';
13
+ export const uploadCommand = defineCommand({
16
14
  meta: {
17
15
  name: 'upload',
18
16
  description: 'Upload an app binary to devicecloud.dev',
19
17
  },
20
18
  args: {
21
- ...api_flags_1.apiFlags,
22
- 'app-url': binary_flags_1.binaryFlags['app-url'],
23
- 'ignore-sha-check': binary_flags_1.binaryFlags['ignore-sha-check'],
24
- debug: output_flags_1.outputFlags.debug,
25
- json: output_flags_1.outputFlags.json,
19
+ ...apiFlags,
20
+ 'app-url': binaryFlags['app-url'],
21
+ 'ignore-sha-check': binaryFlags['ignore-sha-check'],
22
+ debug: outputFlags.debug,
23
+ json: outputFlags.json,
26
24
  appFile: {
27
25
  type: 'positional',
28
26
  required: false,
@@ -35,45 +33,44 @@ exports.uploadCommand = (0, citty_1.defineCommand)({
35
33
  // When --json is set, suppress chatty progress logs so stdout stays parseable.
36
34
  const out = (m) => {
37
35
  if (!json)
38
- cli_1.logger.log(m);
36
+ logger.log(m);
39
37
  };
40
38
  try {
41
39
  const apiKeyFlag = args['api-key'];
42
- const apiUrl = (0, config_store_1.resolveApiUrl)(args['api-url']);
40
+ const apiUrl = resolveApiUrl(args['api-url']);
43
41
  const appUrl = args['app-url'];
44
42
  const ignoreShaCheck = Boolean(args['ignore-sha-check']);
45
43
  const debug = Boolean(args.debug);
46
44
  const positional = args.appFile;
47
- const auth = await (0, auth_1.resolveAuth)({ apiKeyFlag });
45
+ const auth = await resolveAuth({ apiKeyFlag });
48
46
  let resolvedFile = appUrl ?? positional;
49
47
  if (!resolvedFile) {
50
- throw new cli_1.CliError('You must provide an app file path or a signed Expo URL via --app-url');
48
+ throw new CliError('You must provide an app file path or a signed Expo URL via --app-url');
51
49
  }
52
- if ((0, expo_1.isUrl)(resolvedFile)) {
53
- out(` ${styling_1.colors.dim('Downloading Expo build from URL...')}`);
54
- const tarPath = await (0, expo_1.downloadExpoUrl)(resolvedFile, debug);
50
+ if (isUrl(resolvedFile)) {
51
+ out(ui.running('Downloading Expo build from URL'));
52
+ const tarPath = await downloadExpoUrl(resolvedFile, debug);
55
53
  tempFiles.push(tarPath);
56
54
  resolvedFile = tarPath;
57
55
  }
58
56
  if (resolvedFile.endsWith('.tar.gz')) {
59
- out(` ${styling_1.colors.dim('Extracting Expo archive...')}`);
60
- const extractDir = await (0, expo_1.extractTarGz)(resolvedFile, debug);
57
+ out(ui.running('Extracting Expo archive'));
58
+ const extractDir = await extractTarGz(resolvedFile, debug);
61
59
  tempFiles.push(extractDir);
62
- resolvedFile = await (0, expo_1.findAppBundle)(extractDir);
60
+ resolvedFile = await findAppBundle(extractDir);
63
61
  if (debug) {
64
62
  out(`[DEBUG] Found .app bundle at: ${resolvedFile}`);
65
63
  }
66
64
  }
67
65
  if (!['.apk', '.app', '.zip', '.tar.gz'].some((ext) => resolvedFile.endsWith(ext))) {
68
- throw new cli_1.CliError('App file must be a .apk for Android, .app/.zip for iOS, or .tar.gz (Expo iOS build)');
66
+ throw new CliError('App file must be a .apk for Android, .app/.zip for iOS, or .tar.gz (Expo iOS build)');
69
67
  }
70
68
  if (resolvedFile.endsWith('.zip')) {
71
- await (0, methods_1.verifyAppZip)(resolvedFile);
69
+ await verifyAppZip(resolvedFile);
72
70
  }
73
- out((0, styling_1.sectionHeader)('Uploading app binary'));
74
- out(` ${styling_1.colors.dim('→ File:')} ${styling_1.colors.highlight(resolvedFile)}`);
75
- out('');
76
- const appBinaryId = await (0, methods_1.uploadBinary)({
71
+ out(ui.section('Uploading app binary'));
72
+ out(ui.branch(ui.fields([['file', colors.highlight(resolvedFile)]])));
73
+ const appBinaryId = await uploadBinary({
77
74
  auth,
78
75
  apiUrl,
79
76
  debug,
@@ -86,19 +83,21 @@ exports.uploadCommand = (0, citty_1.defineCommand)({
86
83
  console.log(JSON.stringify({ appBinaryId }, null, 2));
87
84
  return;
88
85
  }
89
- cli_1.logger.log(`\n${styling_1.symbols.success} ${styling_1.colors.bold('Upload complete')}`);
90
- cli_1.logger.log(` ${styling_1.colors.dim('Binary ID:')} ${(0, styling_1.formatId)(appBinaryId)}\n`);
91
- cli_1.logger.log(styling_1.colors.dim('You can use this Binary ID in subsequent test runs with:'));
92
- cli_1.logger.log(styling_1.colors.info(`dcd cloud --app-binary-id ${appBinaryId} path/to/flow.yaml\n`));
86
+ logger.log(`\n${ui.success('Upload complete')}`);
87
+ logger.log(ui.branch([
88
+ ...ui.fields([['binary id', formatId(appBinaryId)]]),
89
+ ui.note('Use this Binary ID in subsequent test runs with:'),
90
+ colors.info(`dcd cloud --app-binary-id ${appBinaryId} path/to/flow.yaml`),
91
+ ]));
93
92
  }
94
93
  catch (error) {
95
- cli_1.logger.error(error, { exit: 1, json });
94
+ logger.error(error, { exit: 1, json });
96
95
  }
97
96
  finally {
98
97
  for (const p of tempFiles) {
99
- await (0, promises_1.rm)(p, { recursive: true, force: true }).catch(() => { });
98
+ await rm(p, { recursive: true, force: true }).catch(() => { });
100
99
  }
101
100
  }
102
101
  },
103
102
  });
104
- exports.default = exports.uploadCommand;
103
+ export default uploadCommand;
@@ -1,34 +1,31 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.whoamiCommand = void 0;
4
1
  /**
5
2
  * `dcd whoami` — prints the logged-in user + active org from stored config.
6
3
  */
7
- const citty_1 = require("citty");
8
- const cli_1 = require("../utils/cli");
9
- const config_store_1 = require("../utils/config-store");
10
- const styling_1 = require("../utils/styling");
11
- exports.whoamiCommand = (0, citty_1.defineCommand)({
4
+ import { defineCommand } from 'citty';
5
+ import { logger } from '../utils/cli.js';
6
+ import { readConfig } from '../utils/config-store.js';
7
+ import { colors, formatId } from '../utils/styling.js';
8
+ import { ui } from '../utils/ui.js';
9
+ export const whoamiCommand = defineCommand({
12
10
  meta: {
13
11
  name: 'whoami',
14
12
  description: 'Show the logged-in user and active organization',
15
13
  },
16
14
  run() {
17
- const config = (0, config_store_1.readConfig)();
15
+ const config = readConfig();
18
16
  if (!config?.session) {
19
- cli_1.logger.log(`${styling_1.symbols.info} Not logged in. Run ${styling_1.colors.highlight('dcd login')} or set ${styling_1.colors.highlight('DEVICE_CLOUD_API_KEY')}.`);
17
+ logger.log(ui.info(`Not logged in. Run ${colors.highlight('dcd login')} or set ${colors.highlight('DEVICE_CLOUD_API_KEY')}.`));
20
18
  return;
21
19
  }
22
- cli_1.logger.log((0, styling_1.sectionHeader)('devicecloud.dev'));
23
- cli_1.logger.log(` ${styling_1.colors.dim('User:')} ${styling_1.colors.highlight(config.session.user_email)}`);
24
- if (config.current_org_name) {
25
- cli_1.logger.log(` ${styling_1.colors.dim('Org:')} ${styling_1.colors.highlight(config.current_org_name)}`);
20
+ // Fallback for sessions stored before we persisted org name alongside id.
21
+ const org = config.current_org_name ?? config.current_org_id;
22
+ const fields = [['user', formatId(config.session.user_email)]];
23
+ if (org) {
24
+ fields.push(['org', formatId(org)]);
26
25
  }
27
- else if (config.current_org_id) {
28
- // Fallback for sessions stored before we persisted org name alongside id.
29
- cli_1.logger.log(` ${styling_1.colors.dim('Org:')} ${styling_1.colors.highlight(config.current_org_id)}`);
30
- }
31
- cli_1.logger.log(` ${styling_1.colors.dim('Env:')} ${config.env}`);
26
+ fields.push(['env', config.env]);
27
+ logger.log(ui.section('devicecloud.dev'));
28
+ logger.log(ui.branch(ui.fields(fields)));
32
29
  },
33
30
  });
34
- exports.default = exports.whoamiCommand;
31
+ export default whoamiCommand;
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * Per-env devicecloud.dev coordinates — single source of truth for the URLs
4
3
  * and Supabase credentials the CLI uses.
@@ -9,12 +8,7 @@
9
8
  * them here — every Supabase-based product ships its anon key the same way.
10
9
  * https://supabase.com/docs/guides/api/api-keys
11
10
  */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.ENVIRONMENTS = void 0;
14
- exports.findEnvByApiUrl = findEnvByApiUrl;
15
- exports.inferEnvFromApiUrl = inferEnvFromApiUrl;
16
- exports.resolveFrontendUrl = resolveFrontendUrl;
17
- exports.ENVIRONMENTS = {
11
+ export const ENVIRONMENTS = {
18
12
  prod: {
19
13
  apiUrl: 'https://api.devicecloud.dev',
20
14
  frontendUrl: 'https://console.devicecloud.dev',
@@ -35,13 +29,13 @@ exports.ENVIRONMENTS = {
35
29
  },
36
30
  };
37
31
  /** Exact-match lookup of a known environment by API URL (trailing slashes ignored). */
38
- function findEnvByApiUrl(apiUrl) {
32
+ export function findEnvByApiUrl(apiUrl) {
39
33
  const normalize = (u) => u.replace(/\/+$/, '');
40
34
  const needle = normalize(apiUrl);
41
- return Object.values(exports.ENVIRONMENTS).find((env) => normalize(env.apiUrl) === needle);
35
+ return Object.values(ENVIRONMENTS).find((env) => normalize(env.apiUrl) === needle);
42
36
  }
43
37
  /** Map a caller-supplied API URL to one of the known environments. */
44
- function inferEnvFromApiUrl(apiUrl) {
38
+ export function inferEnvFromApiUrl(apiUrl) {
45
39
  if (apiUrl.includes('api.dev.') || apiUrl.includes('localhost'))
46
40
  return 'dev';
47
41
  return 'prod';
@@ -51,8 +45,8 @@ function inferEnvFromApiUrl(apiUrl) {
51
45
  * `ENVIRONMENTS[inferEnvFromApiUrl(apiUrl)].frontendUrl`, except that when the
52
46
  * API is running on localhost we assume the frontend is too (vite dev server).
53
47
  */
54
- function resolveFrontendUrl(apiUrl) {
48
+ export function resolveFrontendUrl(apiUrl) {
55
49
  if (apiUrl.includes('localhost'))
56
50
  return 'http://localhost:5173';
57
- return exports.ENVIRONMENTS[inferEnvFromApiUrl(apiUrl)].frontendUrl;
51
+ return ENVIRONMENTS[inferEnvFromApiUrl(apiUrl)].frontendUrl;
58
52
  }
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apiFlags = void 0;
4
1
  /**
5
2
  * API authentication and configuration flags
6
3
  */
7
- exports.apiFlags = {
4
+ export const apiFlags = {
8
5
  'api-key': {
9
6
  type: 'string',
10
7
  alias: ['apiKey'],
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.binaryFlags = void 0;
4
1
  /**
5
2
  * Binary upload and management flags
6
3
  */
7
- exports.binaryFlags = {
4
+ export const binaryFlags = {
8
5
  'app-binary-id': {
9
6
  type: 'string',
10
7
  description: 'The ID of the app binary previously uploaded to devicecloud.dev',
@@ -1,15 +1,12 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deviceFlags = void 0;
4
- const device_types_1 = require("../../types/domain/device.types");
5
- const androidApiLevels = Object.values(device_types_1.EAndroidApiLevels).join(', ');
6
- const androidDevices = Object.values(device_types_1.EAndroidDevices).join(', ');
7
- const iosDevices = Object.values(device_types_1.EiOSDevices).join(', ');
8
- const iosVersions = Object.values(device_types_1.EiOSVersions).join(', ');
1
+ import { EAndroidApiLevels, EAndroidDevices, EiOSDevices, EiOSVersions, } from '../../types/domain/device.types.js';
2
+ const androidApiLevels = Object.values(EAndroidApiLevels).join(', ');
3
+ const androidDevices = Object.values(EAndroidDevices).join(', ');
4
+ const iosDevices = Object.values(EiOSDevices).join(', ');
5
+ const iosVersions = Object.values(EiOSVersions).join(', ');
9
6
  /**
10
7
  * Device-specific flags for Android and iOS configuration
11
8
  */
12
- exports.deviceFlags = {
9
+ export const deviceFlags = {
13
10
  'android-api-level': {
14
11
  type: 'string',
15
12
  description: `[Android only] Android API level to run your flow against (options: ${androidApiLevels})`,
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.environmentFlags = void 0;
4
1
  /**
5
2
  * Environment configuration and metadata flags
6
3
  */
7
- exports.environmentFlags = {
4
+ export const environmentFlags = {
8
5
  env: {
9
6
  type: 'string',
10
7
  alias: ['e'],
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executionFlags = void 0;
4
1
  /**
5
2
  * Test execution and flow management flags
6
3
  */
7
- exports.executionFlags = {
4
+ export const executionFlags = {
8
5
  config: {
9
6
  type: 'string',
10
7
  description: 'Path to custom config.yaml file. If not provided, defaults to config.yaml in root flows folders.',
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.githubFlags = void 0;
4
1
  /**
5
2
  * GitHub context flags — stored as gh_* metadata alongside the run
6
3
  */
7
- exports.githubFlags = {
4
+ export const githubFlags = {
8
5
  branch: {
9
6
  type: 'string',
10
7
  description: 'Git branch name for this run (stored as gh_branch metadata)',
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.outputFlags = void 0;
4
1
  /**
5
2
  * Output format and artifact download flags
6
3
  */
7
- exports.outputFlags = {
4
+ export const outputFlags = {
8
5
  'artifacts-path': {
9
6
  type: 'string',
10
7
  description: 'Custom file path for downloaded artifacts (default: ./artifacts.zip). Requires --download-artifacts.',
package/dist/constants.js CHANGED
@@ -1,27 +1,24 @@
1
- "use strict";
2
1
  /**
3
2
  * Consolidated flag definitions.
4
3
  * Flags are organized by domain in src/config/flags/
5
4
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.flags = void 0;
8
- const api_flags_1 = require("./config/flags/api.flags");
9
- const binary_flags_1 = require("./config/flags/binary.flags");
10
- const device_flags_1 = require("./config/flags/device.flags");
11
- const environment_flags_1 = require("./config/flags/environment.flags");
12
- const execution_flags_1 = require("./config/flags/execution.flags");
13
- const github_flags_1 = require("./config/flags/github.flags");
14
- const output_flags_1 = require("./config/flags/output.flags");
5
+ import { apiFlags } from './config/flags/api.flags.js';
6
+ import { binaryFlags } from './config/flags/binary.flags.js';
7
+ import { deviceFlags } from './config/flags/device.flags.js';
8
+ import { environmentFlags } from './config/flags/environment.flags.js';
9
+ import { executionFlags } from './config/flags/execution.flags.js';
10
+ import { githubFlags } from './config/flags/github.flags.js';
11
+ import { outputFlags } from './config/flags/output.flags.js';
15
12
  /**
16
13
  * All flag definitions consolidated from domain-specific flag modules.
17
14
  * Spread into a command's `args` to expose the full cloud-command surface.
18
15
  */
19
- exports.flags = {
20
- ...api_flags_1.apiFlags,
21
- ...binary_flags_1.binaryFlags,
22
- ...device_flags_1.deviceFlags,
23
- ...environment_flags_1.environmentFlags,
24
- ...execution_flags_1.executionFlags,
25
- ...github_flags_1.githubFlags,
26
- ...output_flags_1.outputFlags,
16
+ export const flags = {
17
+ ...apiFlags,
18
+ ...binaryFlags,
19
+ ...deviceFlags,
20
+ ...environmentFlags,
21
+ ...executionFlags,
22
+ ...githubFlags,
23
+ ...outputFlags,
27
24
  };
@@ -1,6 +1,7 @@
1
- import { TAppMetadata } from '../types';
2
- import type { AuthContext } from '../types/domain/auth.types';
3
- import type { LiveCommandStatus, LiveExecResult, LiveSession, LiveSessionSummary } from '../types/domain/live.types';
1
+ import { TAppMetadata } from '../types.js';
2
+ import type { AuthContext } from '../types/domain/auth.types.js';
3
+ import type { LiveCommandStatus, LiveExecResult, LiveSession, LiveSessionSummary } from '../types/domain/live.types.js';
4
+ import { components } from '../types/generated/schema.types.js';
4
5
  /**
5
6
  * Error thrown for non-OK API responses, carrying the HTTP status so callers
6
7
  * can branch on auth failures etc. without string matching.
@@ -51,13 +52,13 @@ export declare const ApiGateway: {
51
52
  tempPath: string;
52
53
  finalPath: string;
53
54
  id: string;
54
- b2?: import("../types/generated/schema.types").components["schemas"]["B2UploadStrategy"];
55
+ b2?: components["schemas"]["B2UploadStrategy"];
55
56
  token?: string;
56
57
  }>;
57
58
  finishLargeFile(baseUrl: string, auth: AuthContext, fileId: string, partSha1Array: string[]): Promise<unknown>;
58
59
  getResultsForUpload(baseUrl: string, auth: AuthContext, uploadId: string): Promise<{
59
60
  statusCode?: number;
60
- results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
61
+ results?: components["schemas"]["TResultResponse"][];
61
62
  }>;
62
63
  getUploadStatus(baseUrl: string, auth: AuthContext, options: {
63
64
  name?: string;
@@ -93,7 +94,31 @@ export declare const ApiGateway: {
93
94
  }>;
94
95
  uploadFlow(baseUrl: string, auth: AuthContext, testFormData: FormData): Promise<{
95
96
  message?: string;
96
- results?: import("../types/generated/schema.types").components["schemas"]["IDBResult"][];
97
+ results?: components["schemas"]["IDBResult"][];
98
+ }>;
99
+ /**
100
+ * Requests a storage URL for a client-direct flow zip upload. Mirrors
101
+ * `getBinaryUploadUrl` (same response shape) but stages the zip under
102
+ * `<orgId>/tests/` instead of the app-binary path. A 404 here means the API
103
+ * predates the client-direct flow path — callers fall back to `uploadFlow`.
104
+ */
105
+ getFlowUploadUrl(baseUrl: string, auth: AuthContext, fileSize: number): Promise<{
106
+ path: string;
107
+ tempPath: string;
108
+ finalPath: string;
109
+ id: string;
110
+ b2?: components["schemas"]["B2UploadStrategy"];
111
+ token?: string;
112
+ }>;
113
+ /**
114
+ * Submits a flow test that references an already-uploaded zip (JSON body, no
115
+ * multipart). The response is identical to the legacy `POST /uploads/flow`.
116
+ * A 404 means the API predates this endpoint — callers fall back to
117
+ * `uploadFlow`.
118
+ */
119
+ submitFlowTest(baseUrl: string, auth: AuthContext, body: Record<string, unknown>): Promise<{
120
+ message?: string;
121
+ results?: components["schemas"]["IDBResult"][];
97
122
  }>;
98
123
  /**
99
124
  * Generic report download method that handles both junit and allure reports