@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.
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/commands/artifacts.d.ts +28 -28
- package/dist/commands/artifacts.js +20 -23
- package/dist/commands/cloud.d.ts +57 -57
- package/dist/commands/cloud.js +224 -192
- package/dist/commands/list.d.ts +22 -22
- package/dist/commands/list.js +43 -40
- package/dist/commands/live.js +134 -127
- package/dist/commands/login.d.ts +11 -11
- package/dist/commands/login.js +46 -44
- package/dist/commands/logout.js +16 -18
- package/dist/commands/status.d.ts +11 -11
- package/dist/commands/status.js +53 -44
- package/dist/commands/switch-org.d.ts +7 -7
- package/dist/commands/switch-org.js +19 -21
- package/dist/commands/upgrade.js +41 -33
- package/dist/commands/upload.d.ts +10 -10
- package/dist/commands/upload.js +42 -43
- package/dist/commands/whoami.js +17 -20
- package/dist/config/environments.js +6 -12
- package/dist/config/flags/api.flags.js +1 -4
- package/dist/config/flags/binary.flags.js +1 -4
- package/dist/config/flags/device.flags.js +6 -9
- package/dist/config/flags/environment.flags.js +1 -4
- package/dist/config/flags/execution.flags.js +1 -4
- package/dist/config/flags/github.flags.js +1 -4
- package/dist/config/flags/output.flags.js +1 -4
- package/dist/constants.js +15 -18
- package/dist/gateways/api-gateway.d.ts +31 -6
- package/dist/gateways/api-gateway.js +70 -16
- package/dist/gateways/cli-auth-gateway.d.ts +1 -1
- package/dist/gateways/cli-auth-gateway.js +3 -6
- package/dist/gateways/realtime-gateway.d.ts +32 -0
- package/dist/gateways/realtime-gateway.js +103 -0
- package/dist/gateways/supabase-gateway.d.ts +1 -1
- package/dist/gateways/supabase-gateway.js +10 -14
- package/dist/index.js +41 -38
- package/dist/mcp/context.d.ts +33 -0
- package/dist/mcp/context.js +33 -0
- package/dist/mcp/helpers.d.ts +16 -0
- package/dist/mcp/helpers.js +34 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +24 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +27 -0
- package/dist/mcp/tools/download-artifacts.d.ts +11 -0
- package/dist/mcp/tools/download-artifacts.js +84 -0
- package/dist/mcp/tools/get-status.d.ts +7 -0
- package/dist/mcp/tools/get-status.js +39 -0
- package/dist/mcp/tools/list-devices.d.ts +7 -0
- package/dist/mcp/tools/list-devices.js +27 -0
- package/dist/mcp/tools/list-runs.d.ts +3 -0
- package/dist/mcp/tools/list-runs.js +60 -0
- package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
- package/dist/mcp/tools/run-cloud-test.js +233 -0
- package/dist/methods.d.ts +32 -1
- package/dist/methods.js +133 -79
- package/dist/services/device-validation.service.d.ts +1 -1
- package/dist/services/device-validation.service.js +1 -5
- package/dist/services/execution-plan.service.js +14 -17
- package/dist/services/execution-plan.utils.js +15 -23
- package/dist/services/flow-paths.d.ts +17 -0
- package/dist/services/flow-paths.js +52 -0
- package/dist/services/metadata-extractor.service.js +22 -25
- package/dist/services/moropo.service.js +18 -20
- package/dist/services/report-download.service.d.ts +1 -1
- package/dist/services/report-download.service.js +5 -9
- package/dist/services/results-polling.service.d.ts +18 -3
- package/dist/services/results-polling.service.js +211 -108
- package/dist/services/telemetry.service.d.ts +10 -1
- package/dist/services/telemetry.service.js +40 -18
- package/dist/services/test-submission.service.d.ts +21 -4
- package/dist/services/test-submission.service.js +51 -34
- package/dist/services/version.service.d.ts +30 -7
- package/dist/services/version.service.js +88 -32
- package/dist/types/domain/auth.types.d.ts +8 -0
- package/dist/types/domain/auth.types.js +1 -2
- package/dist/types/domain/device.types.js +8 -11
- package/dist/types/domain/live.types.js +1 -2
- package/dist/types/generated/schema.types.js +1 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -18
- package/dist/types.js +1 -2
- package/dist/utils/auth.d.ts +1 -1
- package/dist/utils/auth.js +27 -28
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +39 -0
- package/dist/utils/cli.d.ts +16 -2
- package/dist/utils/cli.js +57 -29
- package/dist/utils/compatibility.d.ts +1 -1
- package/dist/utils/compatibility.js +5 -7
- package/dist/utils/config-store.js +33 -43
- package/dist/utils/connectivity.js +1 -4
- package/dist/utils/expo.js +15 -21
- package/dist/utils/orgs.js +8 -12
- package/dist/utils/paths.js +2 -5
- package/dist/utils/progress.d.ts +3 -0
- package/dist/utils/progress.js +47 -8
- package/dist/utils/styling.d.ts +35 -37
- package/dist/utils/styling.js +52 -86
- package/dist/utils/ui.d.ts +41 -0
- package/dist/utils/ui.js +95 -0
- package/package.json +27 -24
package/dist/utils/progress.js
CHANGED
|
@@ -1,25 +1,49 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ux = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Progress adapter that wraps @clack/prompts spinner with a
|
|
6
3
|
* drop-in API for existing services that used oclif's `ux.action` / `ux.info`.
|
|
7
4
|
*
|
|
8
5
|
* Keeps call sites unchanged while migrating away from @oclif/core.
|
|
6
|
+
*
|
|
7
|
+
* TTY-awareness: @clack/prompts' spinner animates on a timer and, when stdout
|
|
8
|
+
* isn't a TTY (CI, pipes, redirects), it can't rewrite a line in place — every
|
|
9
|
+
* frame becomes a fresh line, flooding logs with hundreds of duplicates. In
|
|
10
|
+
* non-interactive environments we skip the spinner entirely and instead print a
|
|
11
|
+
* plain line once per *distinct* status, so CI logs show real progress without
|
|
12
|
+
* the flood.
|
|
9
13
|
*/
|
|
10
|
-
|
|
14
|
+
import * as p from '@clack/prompts';
|
|
15
|
+
import { isCI } from './ci.js';
|
|
11
16
|
class Action {
|
|
12
17
|
current = null;
|
|
13
18
|
_status = '';
|
|
19
|
+
// Last line emitted in non-interactive mode, for de-duplication.
|
|
20
|
+
_lastPrinted = '';
|
|
21
|
+
interactive() {
|
|
22
|
+
return process.stdout.isTTY === true && !isCI();
|
|
23
|
+
}
|
|
14
24
|
start(title, initialStatus, _opts) {
|
|
25
|
+
this._status = initialStatus ?? '';
|
|
26
|
+
const line = initialStatus ? `${title} — ${initialStatus}` : title;
|
|
27
|
+
if (!this.interactive()) {
|
|
28
|
+
this.current = null;
|
|
29
|
+
this.print(line);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
15
32
|
if (this.current) {
|
|
16
33
|
this.current.stop();
|
|
17
34
|
}
|
|
18
35
|
this.current = p.spinner();
|
|
19
|
-
this.
|
|
20
|
-
this.current.start(initialStatus ? `${title} — ${initialStatus}` : title);
|
|
36
|
+
this.current.start(line);
|
|
21
37
|
}
|
|
22
38
|
stop(message) {
|
|
39
|
+
if (!this.interactive()) {
|
|
40
|
+
if (message)
|
|
41
|
+
this.print(message);
|
|
42
|
+
this.current = null;
|
|
43
|
+
this._status = '';
|
|
44
|
+
this._lastPrinted = '';
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
23
47
|
if (!this.current) {
|
|
24
48
|
if (message) {
|
|
25
49
|
// eslint-disable-next-line no-console
|
|
@@ -33,15 +57,30 @@ class Action {
|
|
|
33
57
|
}
|
|
34
58
|
set status(value) {
|
|
35
59
|
this._status = value;
|
|
36
|
-
if (
|
|
60
|
+
if (!value)
|
|
61
|
+
return;
|
|
62
|
+
if (!this.interactive()) {
|
|
63
|
+
this.print(value);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.current) {
|
|
37
67
|
this.current.message(value);
|
|
38
68
|
}
|
|
39
69
|
}
|
|
40
70
|
get status() {
|
|
41
71
|
return this._status;
|
|
42
72
|
}
|
|
73
|
+
// Emit a line only when it differs from the previous one, so repeated polls
|
|
74
|
+
// with no state change stay quiet.
|
|
75
|
+
print(line) {
|
|
76
|
+
if (line === this._lastPrinted)
|
|
77
|
+
return;
|
|
78
|
+
this._lastPrinted = line;
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log(line);
|
|
81
|
+
}
|
|
43
82
|
}
|
|
44
|
-
|
|
83
|
+
export const ux = {
|
|
45
84
|
action: new Action(),
|
|
46
85
|
info(message) {
|
|
47
86
|
// eslint-disable-next-line no-console
|
package/dist/utils/styling.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Centralized styling utilities for CLI output
|
|
3
|
+
* Provides consistent, developer-friendly visual formatting
|
|
4
|
+
*/
|
|
5
|
+
/** Strip ANSI color escape sequences for visible-width calculations. */
|
|
6
|
+
export declare const stripAnsi: (s: string) => string;
|
|
2
7
|
/**
|
|
3
8
|
* Status symbols with associated colors
|
|
4
9
|
*/
|
|
@@ -17,50 +22,49 @@ export declare const symbols: {
|
|
|
17
22
|
* Color utility functions for semantic styling
|
|
18
23
|
*/
|
|
19
24
|
export declare const colors: {
|
|
20
|
-
readonly bold: chalk.
|
|
21
|
-
readonly dim: chalk.
|
|
22
|
-
readonly error: chalk.
|
|
23
|
-
readonly highlight: chalk.
|
|
24
|
-
readonly info: chalk.
|
|
25
|
-
readonly success: chalk.
|
|
26
|
-
readonly url: chalk.
|
|
27
|
-
readonly warning: chalk.
|
|
25
|
+
readonly bold: import("chalk").ChalkInstance;
|
|
26
|
+
readonly dim: import("chalk").ChalkInstance;
|
|
27
|
+
readonly error: import("chalk").ChalkInstance;
|
|
28
|
+
readonly highlight: import("chalk").ChalkInstance;
|
|
29
|
+
readonly info: import("chalk").ChalkInstance;
|
|
30
|
+
readonly success: import("chalk").ChalkInstance;
|
|
31
|
+
readonly url: import("chalk").ChalkInstance;
|
|
32
|
+
readonly warning: import("chalk").ChalkInstance;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Structural glyphs that give the CLI its tree-shaped layout. `section` marks a
|
|
36
|
+
* top-level heading; `branch` opens the group of detail rows beneath it.
|
|
37
|
+
* See STYLE_GUIDE.md.
|
|
38
|
+
*/
|
|
39
|
+
export declare const glyphs: {
|
|
40
|
+
readonly branch: "⎿";
|
|
41
|
+
readonly section: "⏺";
|
|
28
42
|
};
|
|
29
43
|
/**
|
|
30
|
-
*
|
|
44
|
+
* The single source of truth mapping a run/test status to its colour and
|
|
45
|
+
* (already-coloured) symbol. Both {@link formatStatus} and the `ui` status
|
|
46
|
+
* helpers build on this, so every status reads identically everywhere.
|
|
47
|
+
* @param status - The status string (case-insensitive)
|
|
31
48
|
*/
|
|
32
|
-
export declare
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
readonly short: string;
|
|
49
|
+
export declare function statusPalette(status: string): {
|
|
50
|
+
color: (s: string) => string;
|
|
51
|
+
symbol: string;
|
|
36
52
|
};
|
|
37
53
|
/**
|
|
38
|
-
* Format a status
|
|
54
|
+
* Format a status as a coloured symbol followed by the lowercased status word,
|
|
55
|
+
* e.g. `✓ passed`.
|
|
39
56
|
* @param status - The status string to format
|
|
40
57
|
* @returns Formatted status string with color and symbol
|
|
41
58
|
*/
|
|
42
59
|
export declare function formatStatus(status: string): string;
|
|
43
60
|
/**
|
|
44
|
-
* Format a section header
|
|
61
|
+
* Format a top-level section header in the tree style: a `⏺` marker followed by
|
|
62
|
+
* the bold title, preceded by a blank line for separation. Detail rows belong
|
|
63
|
+
* underneath in a branch group (see the `ui` helpers).
|
|
45
64
|
* @param title - The title of the section
|
|
46
65
|
* @returns Formatted section header
|
|
47
66
|
*/
|
|
48
67
|
export declare function sectionHeader(title: string): string;
|
|
49
|
-
/**
|
|
50
|
-
* Format a key-value pair with optional icon
|
|
51
|
-
* @param icon - Icon to display before the key
|
|
52
|
-
* @param key - The key name
|
|
53
|
-
* @param value - The value to display
|
|
54
|
-
* @returns Formatted key-value string
|
|
55
|
-
*/
|
|
56
|
-
export declare function keyValue(icon: string, key: string, value: string): string;
|
|
57
|
-
/**
|
|
58
|
-
* Format a list item
|
|
59
|
-
* @param text - The text of the list item
|
|
60
|
-
* @param prefix - The prefix character (default: '•')
|
|
61
|
-
* @returns Formatted list item
|
|
62
|
-
*/
|
|
63
|
-
export declare function listItem(text: string, prefix?: string): string;
|
|
64
68
|
/**
|
|
65
69
|
* Format a URL
|
|
66
70
|
* @param url - The URL to format
|
|
@@ -87,12 +91,6 @@ export declare function formatTestSummary(summary: {
|
|
|
87
91
|
running: number;
|
|
88
92
|
total: number;
|
|
89
93
|
}): string;
|
|
90
|
-
/**
|
|
91
|
-
* Format a box with content
|
|
92
|
-
* @param content - The content to display in the box
|
|
93
|
-
* @returns Formatted box with borders
|
|
94
|
-
*/
|
|
95
|
-
export declare function box(content: string): string;
|
|
96
94
|
/**
|
|
97
95
|
* Minimal column table renderer.
|
|
98
96
|
* Columns are defined with a `get(row) => string` accessor and optional header label.
|
package/dist/utils/styling.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.dividers = exports.colors = exports.symbols = void 0;
|
|
4
|
-
exports.formatStatus = formatStatus;
|
|
5
|
-
exports.sectionHeader = sectionHeader;
|
|
6
|
-
exports.keyValue = keyValue;
|
|
7
|
-
exports.listItem = listItem;
|
|
8
|
-
exports.formatUrl = formatUrl;
|
|
9
|
-
exports.formatId = formatId;
|
|
10
|
-
exports.formatTestSummary = formatTestSummary;
|
|
11
|
-
exports.box = box;
|
|
12
|
-
exports.table = table;
|
|
13
|
-
exports.getConsoleUrl = getConsoleUrl;
|
|
14
|
-
const chalk = require("chalk");
|
|
15
|
-
const environments_1 = require("../config/environments");
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findEnvByApiUrl } from '../config/environments.js';
|
|
16
3
|
/**
|
|
17
4
|
* Centralized styling utilities for CLI output
|
|
18
5
|
* Provides consistent, developer-friendly visual formatting
|
|
19
6
|
*/
|
|
20
7
|
/** Strip ANSI color escape sequences for visible-width calculations. */
|
|
21
8
|
// eslint-disable-next-line no-control-regex -- matches ANSI escape sequences
|
|
22
|
-
const stripAnsi = (s) => s.replace(/\u001B\[[0-9;]*m/g, '');
|
|
9
|
+
export const stripAnsi = (s) => s.replace(/\u001B\[[0-9;]*m/g, '');
|
|
23
10
|
/**
|
|
24
11
|
* Status symbols with associated colors
|
|
25
12
|
*/
|
|
26
|
-
|
|
13
|
+
export const symbols = {
|
|
27
14
|
cancelled: chalk.gray('⊘'),
|
|
28
15
|
error: chalk.red('✗'),
|
|
29
16
|
info: chalk.blue('ℹ'),
|
|
@@ -37,7 +24,7 @@ exports.symbols = {
|
|
|
37
24
|
/**
|
|
38
25
|
* Color utility functions for semantic styling
|
|
39
26
|
*/
|
|
40
|
-
|
|
27
|
+
export const colors = {
|
|
41
28
|
bold: chalk.bold,
|
|
42
29
|
dim: chalk.gray,
|
|
43
30
|
error: chalk.red,
|
|
@@ -48,125 +35,104 @@ exports.colors = {
|
|
|
48
35
|
warning: chalk.yellow,
|
|
49
36
|
};
|
|
50
37
|
/**
|
|
51
|
-
*
|
|
38
|
+
* Structural glyphs that give the CLI its tree-shaped layout. `section` marks a
|
|
39
|
+
* top-level heading; `branch` opens the group of detail rows beneath it.
|
|
40
|
+
* See STYLE_GUIDE.md.
|
|
52
41
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
short: chalk.gray('─'.repeat(40)),
|
|
42
|
+
export const glyphs = {
|
|
43
|
+
branch: '⎿',
|
|
44
|
+
section: '⏺',
|
|
57
45
|
};
|
|
58
46
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
47
|
+
* The single source of truth mapping a run/test status to its colour and
|
|
48
|
+
* (already-coloured) symbol. Both {@link formatStatus} and the `ui` status
|
|
49
|
+
* helpers build on this, so every status reads identically everywhere.
|
|
50
|
+
* @param status - The status string (case-insensitive)
|
|
62
51
|
*/
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
switch (statusUpper) {
|
|
52
|
+
export function statusPalette(status) {
|
|
53
|
+
switch (status.toUpperCase()) {
|
|
66
54
|
case 'PASSED': {
|
|
67
|
-
return
|
|
55
|
+
return { color: colors.success, symbol: symbols.success };
|
|
68
56
|
}
|
|
57
|
+
case 'ERROR':
|
|
69
58
|
case 'FAILED': {
|
|
70
|
-
return
|
|
59
|
+
return { color: colors.error, symbol: symbols.error };
|
|
71
60
|
}
|
|
72
61
|
case 'RUNNING': {
|
|
73
|
-
return
|
|
62
|
+
return { color: colors.info, symbol: symbols.running };
|
|
74
63
|
}
|
|
75
64
|
case 'PENDING': {
|
|
76
|
-
return
|
|
65
|
+
return { color: colors.warning, symbol: symbols.pending };
|
|
77
66
|
}
|
|
78
67
|
case 'QUEUED': {
|
|
79
|
-
return
|
|
68
|
+
return { color: colors.dim, symbol: symbols.queued };
|
|
80
69
|
}
|
|
81
70
|
case 'CANCELLED': {
|
|
82
|
-
return
|
|
71
|
+
return { color: colors.dim, symbol: symbols.cancelled };
|
|
83
72
|
}
|
|
84
73
|
default: {
|
|
85
|
-
return
|
|
74
|
+
return { color: colors.dim, symbol: symbols.unknown };
|
|
86
75
|
}
|
|
87
76
|
}
|
|
88
77
|
}
|
|
89
78
|
/**
|
|
90
|
-
* Format a
|
|
91
|
-
*
|
|
92
|
-
* @
|
|
93
|
-
|
|
94
|
-
function sectionHeader(title) {
|
|
95
|
-
return `\n${exports.colors.bold(title)}\n${exports.dividers.light}`;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Format a key-value pair with optional icon
|
|
99
|
-
* @param icon - Icon to display before the key
|
|
100
|
-
* @param key - The key name
|
|
101
|
-
* @param value - The value to display
|
|
102
|
-
* @returns Formatted key-value string
|
|
79
|
+
* Format a status as a coloured symbol followed by the lowercased status word,
|
|
80
|
+
* e.g. `✓ passed`.
|
|
81
|
+
* @param status - The status string to format
|
|
82
|
+
* @returns Formatted status string with color and symbol
|
|
103
83
|
*/
|
|
104
|
-
function
|
|
105
|
-
|
|
84
|
+
export function formatStatus(status) {
|
|
85
|
+
const { color, symbol } = statusPalette(status);
|
|
86
|
+
return `${symbol} ${color(status.toLowerCase())}`;
|
|
106
87
|
}
|
|
107
88
|
/**
|
|
108
|
-
* Format a
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* @
|
|
89
|
+
* Format a top-level section header in the tree style: a `⏺` marker followed by
|
|
90
|
+
* the bold title, preceded by a blank line for separation. Detail rows belong
|
|
91
|
+
* underneath in a branch group (see the `ui` helpers).
|
|
92
|
+
* @param title - The title of the section
|
|
93
|
+
* @returns Formatted section header
|
|
112
94
|
*/
|
|
113
|
-
function
|
|
114
|
-
return
|
|
95
|
+
export function sectionHeader(title) {
|
|
96
|
+
return `\n${colors.dim(glyphs.section)} ${colors.bold(title)}`;
|
|
115
97
|
}
|
|
116
98
|
/**
|
|
117
99
|
* Format a URL
|
|
118
100
|
* @param url - The URL to format
|
|
119
101
|
* @returns Formatted URL with styling
|
|
120
102
|
*/
|
|
121
|
-
function formatUrl(url) {
|
|
122
|
-
return
|
|
103
|
+
export function formatUrl(url) {
|
|
104
|
+
return colors.url(url);
|
|
123
105
|
}
|
|
124
106
|
/**
|
|
125
107
|
* Format an ID or identifier
|
|
126
108
|
* @param id - The ID to format
|
|
127
109
|
* @returns Formatted ID with highlighting
|
|
128
110
|
*/
|
|
129
|
-
function formatId(id) {
|
|
130
|
-
return
|
|
111
|
+
export function formatId(id) {
|
|
112
|
+
return colors.highlight(id);
|
|
131
113
|
}
|
|
132
114
|
/**
|
|
133
115
|
* Format a test summary line
|
|
134
116
|
* @param summary - Object containing test counts
|
|
135
117
|
* @returns Formatted summary string
|
|
136
118
|
*/
|
|
137
|
-
function formatTestSummary(summary) {
|
|
119
|
+
export function formatTestSummary(summary) {
|
|
138
120
|
const parts = [
|
|
139
121
|
chalk.bold(`${summary.completed}/${summary.total}`),
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
122
|
+
colors.success(`✓ ${summary.passed}`),
|
|
123
|
+
colors.error(`✗ ${summary.failed}`),
|
|
124
|
+
colors.info(`▶ ${summary.running}`),
|
|
125
|
+
colors.warning(`⏸ ${summary.pending}`),
|
|
126
|
+
colors.dim(`⏳ ${summary.queued}`),
|
|
145
127
|
];
|
|
146
128
|
return parts.join(' │ ');
|
|
147
129
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Format a box with content
|
|
150
|
-
* @param content - The content to display in the box
|
|
151
|
-
* @returns Formatted box with borders
|
|
152
|
-
*/
|
|
153
|
-
function box(content) {
|
|
154
|
-
const lines = content.split('\n');
|
|
155
|
-
const visibleLen = (s) => stripAnsi(s).length;
|
|
156
|
-
const maxLength = Math.max(...lines.map((l) => visibleLen(l)));
|
|
157
|
-
const top = chalk.gray('┌' + '─'.repeat(maxLength + 2) + '┐');
|
|
158
|
-
const bottom = chalk.gray('└' + '─'.repeat(maxLength + 2) + '┘');
|
|
159
|
-
const middle = lines
|
|
160
|
-
.map((line) => chalk.gray('│ ') + line + ' '.repeat(Math.max(0, maxLength - visibleLen(line))) + chalk.gray(' │'))
|
|
161
|
-
.join('\n');
|
|
162
|
-
return `${top}\n${middle}\n${bottom}`;
|
|
163
|
-
}
|
|
164
130
|
/**
|
|
165
131
|
* Minimal column table renderer.
|
|
166
132
|
* Columns are defined with a `get(row) => string` accessor and optional header label.
|
|
167
133
|
* Matches the subset of oclif's ux.table used by this CLI.
|
|
168
134
|
*/
|
|
169
|
-
function table(rows, columns, options = {}) {
|
|
135
|
+
export function table(rows, columns, options = {}) {
|
|
170
136
|
const printLine = options.printLine ?? ((line) => {
|
|
171
137
|
// eslint-disable-next-line no-console
|
|
172
138
|
console.log(line);
|
|
@@ -194,8 +160,8 @@ function table(rows, columns, options = {}) {
|
|
|
194
160
|
* @param resultId - The result ID
|
|
195
161
|
* @returns The appropriate console URL
|
|
196
162
|
*/
|
|
197
|
-
function getConsoleUrl(apiUrl, uploadId, resultId) {
|
|
198
|
-
const env =
|
|
163
|
+
export function getConsoleUrl(apiUrl, uploadId, resultId) {
|
|
164
|
+
const env = findEnvByApiUrl(apiUrl);
|
|
199
165
|
const base = env?.frontendUrl ?? 'https://dev.console.devicecloud.dev';
|
|
200
166
|
return `${base}/results?upload=${uploadId}&result=${resultId}`;
|
|
201
167
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** A `[label, value]` detail row; the value is passed through already styled. */
|
|
2
|
+
export type Field = [label: string, value: string];
|
|
3
|
+
export declare const ui: {
|
|
4
|
+
/**
|
|
5
|
+
* Render detail rows as a branch group beneath the most recent section:
|
|
6
|
+
*
|
|
7
|
+
* ⎿ row one
|
|
8
|
+
* row two
|
|
9
|
+
*
|
|
10
|
+
* Embedded newlines in a row are kept aligned. Returns `''` for no rows.
|
|
11
|
+
*/
|
|
12
|
+
readonly branch: (rows: string[]) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Align `[label, value]` pairs into `label value` rows for use inside a
|
|
15
|
+
* {@link branch} group. Plain labels are dimmed; labels the caller already
|
|
16
|
+
* styled (e.g. `colors.bold(name)`) are left as-is. Padding is measured on
|
|
17
|
+
* visible width so ANSI colours never throw the columns off.
|
|
18
|
+
*/
|
|
19
|
+
readonly fields: (pairs: Field[]) => string[];
|
|
20
|
+
/** `ℹ message` — neutral, standalone information. */
|
|
21
|
+
readonly info: (message: string) => string;
|
|
22
|
+
/** Dimmed secondary text — hints, tips, "you can close this terminal", etc. */
|
|
23
|
+
readonly note: (message: string) => string;
|
|
24
|
+
/** `▶ message` — an action currently in progress. */
|
|
25
|
+
readonly running: (message: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* A top-level section header — `⏺ Title`, preceded by a blank line. Detail
|
|
28
|
+
* rows belong underneath via {@link branch}.
|
|
29
|
+
*/
|
|
30
|
+
readonly section: (title: string) => string;
|
|
31
|
+
/** A coloured status word + symbol, e.g. `✓ passed`. Delegates to the shared palette. */
|
|
32
|
+
readonly status: (status: string) => string;
|
|
33
|
+
/** The coloured status symbol alone, e.g. green `✓`. */
|
|
34
|
+
readonly statusSymbol: (status: string) => string;
|
|
35
|
+
/** The lowercased status word in its status colour, e.g. green `passed`. */
|
|
36
|
+
readonly statusWord: (status: string) => string;
|
|
37
|
+
/** `✓ message` — a completed action; the message is emphasised. */
|
|
38
|
+
readonly success: (message: string) => string;
|
|
39
|
+
/** `⚠ message` — a non-fatal warning. */
|
|
40
|
+
readonly warn: (message: string) => string;
|
|
41
|
+
};
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified rendering layer for all CLI command output.
|
|
3
|
+
*
|
|
4
|
+
* Every command composes its output from these helpers so the look stays
|
|
5
|
+
* consistent — a tree of `⏺` section headings with `⎿` branch groups of detail
|
|
6
|
+
* rows beneath them, inspired by Claude Code. See STYLE_GUIDE.md for the rules
|
|
7
|
+
* and worked examples.
|
|
8
|
+
*
|
|
9
|
+
* Functions RETURN strings (never write to stdout themselves) so callers route
|
|
10
|
+
* them through their own logger / `--json` gate and they stay unit-testable.
|
|
11
|
+
*/
|
|
12
|
+
import { colors, formatStatus, glyphs, sectionHeader, statusPalette, stripAnsi, symbols, } from './styling.js';
|
|
13
|
+
/**
|
|
14
|
+
* Indentation under a section. The branch glyph sits two columns in and its
|
|
15
|
+
* content begins at column four; continuation rows align to the same column.
|
|
16
|
+
*
|
|
17
|
+
* ⏺ Title
|
|
18
|
+
* ⎿ first detail row
|
|
19
|
+
* continuation row
|
|
20
|
+
*/
|
|
21
|
+
const BRANCH_PREFIX = ` ${colors.dim(glyphs.branch)} `;
|
|
22
|
+
const CONTINUATION_PREFIX = ' ';
|
|
23
|
+
export const ui = {
|
|
24
|
+
/**
|
|
25
|
+
* Render detail rows as a branch group beneath the most recent section:
|
|
26
|
+
*
|
|
27
|
+
* ⎿ row one
|
|
28
|
+
* row two
|
|
29
|
+
*
|
|
30
|
+
* Embedded newlines in a row are kept aligned. Returns `''` for no rows.
|
|
31
|
+
*/
|
|
32
|
+
branch(rows) {
|
|
33
|
+
const lines = rows.flatMap((row) => row.split('\n'));
|
|
34
|
+
if (lines.length === 0) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
return lines
|
|
38
|
+
.map((line, index) => index === 0 ? `${BRANCH_PREFIX}${line}` : `${CONTINUATION_PREFIX}${line}`)
|
|
39
|
+
.join('\n');
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Align `[label, value]` pairs into `label value` rows for use inside a
|
|
43
|
+
* {@link branch} group. Plain labels are dimmed; labels the caller already
|
|
44
|
+
* styled (e.g. `colors.bold(name)`) are left as-is. Padding is measured on
|
|
45
|
+
* visible width so ANSI colours never throw the columns off.
|
|
46
|
+
*/
|
|
47
|
+
fields(pairs) {
|
|
48
|
+
const width = Math.max(0, ...pairs.map(([label]) => stripAnsi(label).length));
|
|
49
|
+
return pairs.map(([label, value]) => {
|
|
50
|
+
const styled = stripAnsi(label) === label ? colors.dim(label) : label;
|
|
51
|
+
const padding = ' '.repeat(Math.max(0, width - stripAnsi(label).length));
|
|
52
|
+
return `${styled}${padding} ${value}`;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
/** `ℹ message` — neutral, standalone information. */
|
|
56
|
+
info(message) {
|
|
57
|
+
return `${symbols.info} ${message}`;
|
|
58
|
+
},
|
|
59
|
+
/** Dimmed secondary text — hints, tips, "you can close this terminal", etc. */
|
|
60
|
+
note(message) {
|
|
61
|
+
return colors.dim(message);
|
|
62
|
+
},
|
|
63
|
+
/** `▶ message` — an action currently in progress. */
|
|
64
|
+
running(message) {
|
|
65
|
+
return `${symbols.running} ${message}`;
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* A top-level section header — `⏺ Title`, preceded by a blank line. Detail
|
|
69
|
+
* rows belong underneath via {@link branch}.
|
|
70
|
+
*/
|
|
71
|
+
section(title) {
|
|
72
|
+
return sectionHeader(title);
|
|
73
|
+
},
|
|
74
|
+
/** A coloured status word + symbol, e.g. `✓ passed`. Delegates to the shared palette. */
|
|
75
|
+
status(status) {
|
|
76
|
+
return formatStatus(status);
|
|
77
|
+
},
|
|
78
|
+
/** The coloured status symbol alone, e.g. green `✓`. */
|
|
79
|
+
statusSymbol(status) {
|
|
80
|
+
return statusPalette(status).symbol;
|
|
81
|
+
},
|
|
82
|
+
/** The lowercased status word in its status colour, e.g. green `passed`. */
|
|
83
|
+
statusWord(status) {
|
|
84
|
+
const { color } = statusPalette(status);
|
|
85
|
+
return color(status.toLowerCase());
|
|
86
|
+
},
|
|
87
|
+
/** `✓ message` — a completed action; the message is emphasised. */
|
|
88
|
+
success(message) {
|
|
89
|
+
return `${symbols.success} ${colors.bold(message)}`;
|
|
90
|
+
},
|
|
91
|
+
/** `⚠ message` — a non-fatal warning. */
|
|
92
|
+
warn(message) {
|
|
93
|
+
return `${symbols.warning} ${message}`;
|
|
94
|
+
},
|
|
95
|
+
};
|
package/package.json
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "devicecloud.dev",
|
|
3
3
|
"bin": {
|
|
4
|
-
"dcd": "dist/index.js"
|
|
4
|
+
"dcd": "dist/index.js",
|
|
5
|
+
"dcd-mcp": "dist/mcp/index.js"
|
|
5
6
|
},
|
|
6
7
|
"dependencies": {
|
|
7
|
-
"@clack/prompts": "^1.
|
|
8
|
-
"@
|
|
8
|
+
"@clack/prompts": "^1.6.0",
|
|
9
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
10
|
+
"@supabase/supabase-js": "^2.108.2",
|
|
9
11
|
"bplist-parser": "^0.3.2",
|
|
10
|
-
"chalk": "
|
|
11
|
-
"citty": "^0.
|
|
12
|
-
"js-yaml": "^
|
|
12
|
+
"chalk": "^5.6.2",
|
|
13
|
+
"citty": "^0.2.2",
|
|
14
|
+
"js-yaml": "^5.0.0",
|
|
13
15
|
"node-apk": "^1.2.1",
|
|
14
16
|
"node-stream-zip": "^1.15.0",
|
|
15
|
-
"plist": "^
|
|
16
|
-
"tar": "^7.5.
|
|
17
|
+
"plist": "^5.0.0",
|
|
18
|
+
"tar": "^7.5.16",
|
|
17
19
|
"tus-js-client": "^4.3.1",
|
|
18
|
-
"yazl": "^3.3.1"
|
|
20
|
+
"yazl": "^3.3.1",
|
|
21
|
+
"zod": "^4.4.3"
|
|
19
22
|
},
|
|
20
|
-
"description": "
|
|
23
|
+
"description": "Run Maestro mobile UI tests in the cloud — upload an iOS/Android build, execute flows across real devices, and stream results. Ships the dcd CLI and a dcd-mcp MCP server.",
|
|
21
24
|
"devDependencies": {
|
|
22
|
-
"@eslint/js": "^
|
|
23
|
-
"@types/chai": "^
|
|
25
|
+
"@eslint/js": "^10.0.1",
|
|
26
|
+
"@types/chai": "^5.2.3",
|
|
24
27
|
"@types/js-yaml": "^4.0.9",
|
|
25
28
|
"@types/mocha": "^10.0.10",
|
|
26
|
-
"@types/node": "^
|
|
27
|
-
"@types/plist": "^3.0.5",
|
|
29
|
+
"@types/node": "^26.0.0",
|
|
28
30
|
"@types/yazl": "^3.3.1",
|
|
29
|
-
"chai": "^
|
|
30
|
-
"eslint": "^
|
|
31
|
+
"chai": "^6.2.2",
|
|
32
|
+
"eslint": "^10.5.0",
|
|
31
33
|
"eslint-config-prettier": "^10.1.8",
|
|
32
34
|
"eslint-plugin-import": "^2.32.0",
|
|
33
|
-
"eslint-plugin-unicorn": "^
|
|
35
|
+
"eslint-plugin-unicorn": "^68.0.0",
|
|
34
36
|
"husky": "^9.1.7",
|
|
35
|
-
"mocha": "^11.7.
|
|
36
|
-
"prettier": "^3.
|
|
37
|
+
"mocha": "^11.7.6",
|
|
38
|
+
"prettier": "^3.8.4",
|
|
37
39
|
"shx": "^0.4.0",
|
|
38
|
-
"tsx": "^4.
|
|
39
|
-
"typescript": "^
|
|
40
|
-
"typescript-eslint": "^8.
|
|
40
|
+
"tsx": "^4.22.4",
|
|
41
|
+
"typescript": "^6.0.3",
|
|
42
|
+
"typescript-eslint": "^8.61.1"
|
|
41
43
|
},
|
|
42
44
|
"engines": {
|
|
43
45
|
"node": ">=22.0.0"
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
"main": "dist/index.js",
|
|
51
53
|
"name": "@devicecloud.dev/dcd",
|
|
52
54
|
"private": false,
|
|
55
|
+
"type": "module",
|
|
53
56
|
"publishConfig": {
|
|
54
57
|
"access": "public"
|
|
55
58
|
},
|
|
@@ -57,7 +60,7 @@
|
|
|
57
60
|
"type": "git",
|
|
58
61
|
"url": "https://devicecloud.dev"
|
|
59
62
|
},
|
|
60
|
-
"version": "5.0.0-beta.
|
|
63
|
+
"version": "5.0.0-beta.2",
|
|
61
64
|
"bugs": {
|
|
62
65
|
"url": "https://discord.gg/gm3mJwcNw8"
|
|
63
66
|
},
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
"types": "dist/index.d.ts",
|
|
73
76
|
"scripts": {
|
|
74
77
|
"dcd": "tsx src/index.ts",
|
|
75
|
-
"build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js",
|
|
78
|
+
"build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js dist/mcp/index.js",
|
|
76
79
|
"build:binaries": "node scripts/build-binaries.mjs",
|
|
77
80
|
"lint": "eslint src test --ext .ts",
|
|
78
81
|
"test": "node scripts/test-runner.mjs",
|