@devicecloud.dev/dcd 4.4.9 → 5.0.0-beta.0
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/README.md +40 -2
- package/dist/commands/artifacts.d.ts +47 -18
- package/dist/commands/artifacts.js +68 -60
- package/dist/commands/cloud.d.ts +228 -88
- package/dist/commands/cloud.js +389 -288
- package/dist/commands/list.d.ts +39 -38
- package/dist/commands/list.js +122 -127
- package/dist/commands/live.d.ts +2 -0
- package/dist/commands/live.js +513 -0
- package/dist/commands/login.d.ts +17 -0
- package/dist/commands/login.js +250 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +32 -0
- package/dist/commands/status.d.ts +23 -42
- package/dist/commands/status.js +162 -173
- package/dist/commands/switch-org.d.ts +12 -0
- package/dist/commands/switch-org.js +78 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +122 -0
- package/dist/commands/upload.d.ts +33 -18
- package/dist/commands/upload.js +62 -67
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +34 -0
- package/dist/config/environments.d.ts +31 -0
- package/dist/config/environments.js +58 -0
- package/dist/config/flags/api.flags.d.ts +10 -2
- package/dist/config/flags/api.flags.js +12 -10
- package/dist/config/flags/binary.flags.d.ts +17 -4
- package/dist/config/flags/binary.flags.js +13 -14
- package/dist/config/flags/device.flags.d.ts +49 -11
- package/dist/config/flags/device.flags.js +41 -33
- package/dist/config/flags/environment.flags.d.ts +27 -6
- package/dist/config/flags/environment.flags.js +23 -25
- package/dist/config/flags/execution.flags.d.ts +35 -8
- package/dist/config/flags/execution.flags.js +30 -37
- package/dist/config/flags/github.flags.d.ts +23 -5
- package/dist/config/flags/github.flags.js +18 -11
- package/dist/config/flags/output.flags.d.ts +57 -13
- package/dist/config/flags/output.flags.js +47 -43
- package/dist/constants.d.ts +218 -51
- package/dist/constants.js +2 -2
- package/dist/gateways/api-gateway.d.ts +43 -12
- package/dist/gateways/api-gateway.js +240 -100
- package/dist/gateways/cli-auth-gateway.d.ts +13 -0
- package/dist/gateways/cli-auth-gateway.js +57 -0
- package/dist/gateways/supabase-gateway.d.ts +11 -11
- package/dist/gateways/supabase-gateway.js +15 -39
- package/dist/index.d.ts +2 -1
- package/dist/index.js +93 -2
- package/dist/methods.d.ts +3 -5
- package/dist/methods.js +170 -178
- package/dist/services/device-validation.service.d.ts +8 -0
- package/dist/services/device-validation.service.js +55 -35
- package/dist/services/execution-plan.service.js +27 -15
- package/dist/services/execution-plan.utils.d.ts +3 -0
- package/dist/services/execution-plan.utils.js +10 -32
- package/dist/services/metadata-extractor.service.d.ts +0 -2
- package/dist/services/metadata-extractor.service.js +57 -57
- package/dist/services/moropo.service.js +25 -24
- package/dist/services/report-download.service.d.ts +12 -1
- package/dist/services/report-download.service.js +31 -20
- package/dist/services/results-polling.service.d.ts +6 -7
- package/dist/services/results-polling.service.js +80 -33
- package/dist/services/telemetry.service.d.ts +40 -0
- package/dist/services/telemetry.service.js +230 -0
- package/dist/services/test-submission.service.js +2 -1
- package/dist/services/version.service.d.ts +3 -2
- package/dist/services/version.service.js +27 -11
- package/dist/types/domain/auth.types.d.ts +12 -0
- package/dist/types/{schema.types.js → domain/auth.types.js} +0 -1
- package/dist/types/domain/live.types.d.ts +76 -0
- package/dist/types/domain/live.types.js +4 -0
- package/dist/utils/auth.d.ts +13 -0
- package/dist/utils/auth.js +142 -0
- package/dist/utils/cli.d.ts +35 -0
- package/dist/utils/cli.js +127 -0
- package/dist/utils/compatibility.d.ts +2 -1
- package/dist/utils/compatibility.js +2 -2
- package/dist/utils/config-store.d.ts +35 -0
- package/dist/utils/config-store.js +125 -0
- package/dist/utils/connectivity.js +7 -3
- package/dist/utils/expo.js +14 -3
- package/dist/utils/orgs.d.ts +11 -0
- package/dist/utils/orgs.js +40 -0
- package/dist/utils/paths.d.ts +11 -0
- package/dist/utils/paths.js +24 -0
- package/dist/utils/progress.d.ts +13 -0
- package/dist/utils/progress.js +50 -0
- package/dist/utils/styling.d.ts +13 -5
- package/dist/utils/styling.js +37 -7
- package/package.json +26 -38
- package/bin/dev.cmd +0 -3
- package/bin/dev.js +0 -6
- package/bin/run.cmd +0 -3
- package/bin/run.js +0 -7
- package/dist/types/schema.types.d.ts +0 -2702
- package/oclif.manifest.json +0 -884
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.getConfigDir = getConfigDir;
|
|
5
|
+
exports.getConfigPath = getConfigPath;
|
|
6
|
+
exports.readConfig = readConfig;
|
|
7
|
+
exports.resolveApiUrl = resolveApiUrl;
|
|
8
|
+
exports.writeConfig = writeConfig;
|
|
9
|
+
exports.clearConfig = clearConfig;
|
|
10
|
+
exports.configFileMode = configFileMode;
|
|
11
|
+
/**
|
|
12
|
+
* Persistent CLI config: Supabase session tokens + chosen org.
|
|
13
|
+
*
|
|
14
|
+
* Storage: $XDG_CONFIG_HOME/dcd/config.json (fallback ~/.dcd/config.json).
|
|
15
|
+
* File mode 0600, directory mode 0700 — owner-only.
|
|
16
|
+
* Writes are atomic (tmp file + rename) so a crash mid-refresh can't
|
|
17
|
+
* leave a corrupt config behind that logs the user out.
|
|
18
|
+
*/
|
|
19
|
+
const node_crypto_1 = require("node:crypto");
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
21
|
+
const node_os_1 = require("node:os");
|
|
22
|
+
const path = require("node:path");
|
|
23
|
+
const environments_1 = require("../config/environments");
|
|
24
|
+
exports.CONFIG_SCHEMA_VERSION = 1;
|
|
25
|
+
function getConfigDir() {
|
|
26
|
+
if (process.env.DCD_CONFIG_DIR)
|
|
27
|
+
return process.env.DCD_CONFIG_DIR;
|
|
28
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
29
|
+
if (xdg && xdg.trim().length > 0)
|
|
30
|
+
return path.join(xdg, 'dcd');
|
|
31
|
+
return path.join((0, node_os_1.homedir)(), '.dcd');
|
|
32
|
+
}
|
|
33
|
+
function getConfigPath() {
|
|
34
|
+
return path.join(getConfigDir(), 'config.json');
|
|
35
|
+
}
|
|
36
|
+
function readConfig() {
|
|
37
|
+
const p = getConfigPath();
|
|
38
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const raw = (0, node_fs_1.readFileSync)(p, 'utf8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (parsed.version !== exports.CONFIG_SCHEMA_VERSION) {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.warn(`Warning: config at ${p} was written by an incompatible CLI version (config version ${parsed.version}); ignoring it. Run \`dcd login\` to recreate it.`);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Surface the corruption instead of silently behaving as logged-out, so
|
|
52
|
+
// downstream "Not authenticated" errors aren't mystifying.
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.warn(`Warning: could not parse config at ${p}; treating as logged out. Run \`dcd login\` to recreate it.`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the API URL a command should talk to. Precedence:
|
|
60
|
+
* 1. explicit --api-url flag
|
|
61
|
+
* 2. api_url stored by `dcd login` (honors the env the user logged into)
|
|
62
|
+
* 3. prod default
|
|
63
|
+
*
|
|
64
|
+
* Without this, session commands default to prod and a dev/staging Bearer
|
|
65
|
+
* token is rejected with a misleading "Invalid or expired JWT". `switch-org`
|
|
66
|
+
* has always done this; this helper extends it to every command.
|
|
67
|
+
*/
|
|
68
|
+
function resolveApiUrl(flag) {
|
|
69
|
+
const explicit = flag?.trim();
|
|
70
|
+
if (explicit)
|
|
71
|
+
return explicit;
|
|
72
|
+
return readConfig()?.api_url ?? environments_1.ENVIRONMENTS.prod.apiUrl;
|
|
73
|
+
}
|
|
74
|
+
function writeConfig(config) {
|
|
75
|
+
const dir = getConfigDir();
|
|
76
|
+
if (!(0, node_fs_1.existsSync)(dir)) {
|
|
77
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 0o700 });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
try {
|
|
81
|
+
(0, node_fs_1.chmodSync)(dir, 0o700);
|
|
82
|
+
}
|
|
83
|
+
catch { /* best effort */ }
|
|
84
|
+
}
|
|
85
|
+
const finalPath = getConfigPath();
|
|
86
|
+
// Best-effort cleanup of orphaned tmp files left behind by crashed writes.
|
|
87
|
+
// Only remove old ones — a concurrent process may be between its own
|
|
88
|
+
// writeFileSync and renameSync right now.
|
|
89
|
+
try {
|
|
90
|
+
const base = path.basename(finalPath);
|
|
91
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir)) {
|
|
92
|
+
if (!entry.startsWith(`${base}.`) || !entry.endsWith('.tmp'))
|
|
93
|
+
continue;
|
|
94
|
+
const tmp = path.join(dir, entry);
|
|
95
|
+
try {
|
|
96
|
+
if (Date.now() - (0, node_fs_1.statSync)(tmp).mtimeMs > 60_000)
|
|
97
|
+
(0, node_fs_1.unlinkSync)(tmp);
|
|
98
|
+
}
|
|
99
|
+
catch { /* best effort */ }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { /* best effort */ }
|
|
103
|
+
const tmpPath = `${finalPath}.${(0, node_crypto_1.randomBytes)(6).toString('hex')}.tmp`;
|
|
104
|
+
(0, node_fs_1.writeFileSync)(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
105
|
+
try {
|
|
106
|
+
(0, node_fs_1.chmodSync)(tmpPath, 0o600);
|
|
107
|
+
}
|
|
108
|
+
catch { /* best effort on platforms w/o chmod */ }
|
|
109
|
+
(0, node_fs_1.renameSync)(tmpPath, finalPath);
|
|
110
|
+
try {
|
|
111
|
+
(0, node_fs_1.chmodSync)(finalPath, 0o600);
|
|
112
|
+
}
|
|
113
|
+
catch { /* best effort */ }
|
|
114
|
+
}
|
|
115
|
+
function clearConfig() {
|
|
116
|
+
const p = getConfigPath();
|
|
117
|
+
if ((0, node_fs_1.existsSync)(p))
|
|
118
|
+
(0, node_fs_1.unlinkSync)(p);
|
|
119
|
+
}
|
|
120
|
+
function configFileMode() {
|
|
121
|
+
const p = getConfigPath();
|
|
122
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
123
|
+
return null;
|
|
124
|
+
return (0, node_fs_1.statSync)(p).mode & 0o777;
|
|
125
|
+
}
|
|
@@ -22,16 +22,15 @@ async function checkInternetConnectivity() {
|
|
|
22
22
|
// Try each endpoint with a short timeout
|
|
23
23
|
for (const { url, description } of testEndpoints) {
|
|
24
24
|
const startTime = Date.now();
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
|
|
25
27
|
try {
|
|
26
|
-
const controller = new AbortController();
|
|
27
|
-
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
|
|
28
28
|
const response = await fetch(url, {
|
|
29
29
|
method: 'HEAD', // Use HEAD to minimize data transfer
|
|
30
30
|
signal: controller.signal,
|
|
31
31
|
// Disable redirects to get faster response
|
|
32
32
|
redirect: 'manual',
|
|
33
33
|
});
|
|
34
|
-
clearTimeout(timeoutId);
|
|
35
34
|
const latencyMs = Date.now() - startTime;
|
|
36
35
|
// Any response (including 3xx redirects) indicates connectivity
|
|
37
36
|
if (response) {
|
|
@@ -81,6 +80,11 @@ async function checkInternetConnectivity() {
|
|
|
81
80
|
// Continue to next endpoint if this one fails
|
|
82
81
|
continue;
|
|
83
82
|
}
|
|
83
|
+
finally {
|
|
84
|
+
// Always clear — a dangling abort timer keeps the event loop alive
|
|
85
|
+
// for up to 3s per failed endpoint.
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
}
|
|
84
88
|
}
|
|
85
89
|
// Generate developer-friendly message
|
|
86
90
|
let message;
|
package/dist/utils/expo.js
CHANGED
|
@@ -40,10 +40,20 @@ async function downloadExpoUrl(url, debug) {
|
|
|
40
40
|
try {
|
|
41
41
|
const response = await fetch(url);
|
|
42
42
|
if (!response.ok) {
|
|
43
|
+
// 4xx responses (expired signed URL, 404, ...) won't get better on
|
|
44
|
+
// retry — flag them so the catch below rethrows immediately.
|
|
45
|
+
const permanent = response.status >= 400 && response.status < 500;
|
|
46
|
+
let error;
|
|
43
47
|
if (response.status === 403 || response.status === 401) {
|
|
44
|
-
|
|
48
|
+
error = new Error(`Failed to download Expo build from URL (HTTP ${response.status}). Expo signed URLs expire after ~1 hour — please generate a fresh URL with 'eas build' and try again.`);
|
|
45
49
|
}
|
|
46
|
-
|
|
50
|
+
else {
|
|
51
|
+
error = new Error(`Failed to download Expo build from URL (HTTP ${response.status}).`);
|
|
52
|
+
}
|
|
53
|
+
if (permanent) {
|
|
54
|
+
error.permanent = true;
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
47
57
|
}
|
|
48
58
|
if (!response.body) {
|
|
49
59
|
throw new Error('No response body received from Expo URL.');
|
|
@@ -60,8 +70,9 @@ async function downloadExpoUrl(url, debug) {
|
|
|
60
70
|
catch (error) {
|
|
61
71
|
// Clean up any partial file before retrying
|
|
62
72
|
await fsp.rm(destPath, { force: true }).catch(() => { });
|
|
73
|
+
const isPermanent = Boolean(error?.permanent);
|
|
63
74
|
const isLastAttempt = attempt === DOWNLOAD_RETRY_ATTEMPTS;
|
|
64
|
-
if (isLastAttempt) {
|
|
75
|
+
if (isPermanent || isLastAttempt) {
|
|
65
76
|
throw error;
|
|
66
77
|
}
|
|
67
78
|
if (debug) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface OrgListItem {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
slug?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function fetchOrgs(apiUrl: string, headers: Record<string, string>): Promise<OrgListItem[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Interactive org picker. Auto-selects when there's only one org (returns it
|
|
9
|
+
* without prompting). Throws on zero orgs or user cancellation.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pickOrg(orgs: OrgListItem[], message?: string): Promise<OrgListItem>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchOrgs = fetchOrgs;
|
|
4
|
+
exports.pickOrg = pickOrg;
|
|
5
|
+
/**
|
|
6
|
+
* Shared helpers for fetching and picking from /me/orgs. Used by both
|
|
7
|
+
* `dcd login` (after the PKCE claim returns a session) and `dcd switch-org`.
|
|
8
|
+
*/
|
|
9
|
+
const p = require("@clack/prompts");
|
|
10
|
+
const cli_1 = require("./cli");
|
|
11
|
+
async function fetchOrgs(apiUrl, headers) {
|
|
12
|
+
const res = await fetch(`${apiUrl.replace(/\/$/, '')}/me/orgs`, { headers });
|
|
13
|
+
if (!res.ok) {
|
|
14
|
+
const body = await res.text().catch(() => '');
|
|
15
|
+
throw new cli_1.CliError(`Failed to list organizations: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
|
|
16
|
+
}
|
|
17
|
+
const body = (await res.json());
|
|
18
|
+
return body.orgs ?? [];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Interactive org picker. Auto-selects when there's only one org (returns it
|
|
22
|
+
* without prompting). Throws on zero orgs or user cancellation.
|
|
23
|
+
*/
|
|
24
|
+
async function pickOrg(orgs, message = 'Pick an organization') {
|
|
25
|
+
if (orgs.length === 0) {
|
|
26
|
+
throw new cli_1.CliError('No organizations found for this user.');
|
|
27
|
+
}
|
|
28
|
+
if (orgs.length === 1)
|
|
29
|
+
return orgs[0];
|
|
30
|
+
const picked = await p.select({
|
|
31
|
+
message,
|
|
32
|
+
options: orgs.map((o) => ({ value: o.id, label: o.name })),
|
|
33
|
+
});
|
|
34
|
+
if (p.isCancel(picked))
|
|
35
|
+
throw new cli_1.CliError('Cancelled.');
|
|
36
|
+
const chosen = orgs.find((o) => o.id === picked);
|
|
37
|
+
if (!chosen)
|
|
38
|
+
throw new cli_1.CliError('No organization selected.');
|
|
39
|
+
return chosen;
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an absolute path into the portable './'-prefixed, forward-slash
|
|
3
|
+
* relative form used as flow keys across submission, metadata maps, and
|
|
4
|
+
* polling. `commonRoot` must be a whole-segment prefix of `absolutePath`
|
|
5
|
+
* without a trailing separator (or '' when no common root exists, in which
|
|
6
|
+
* case the path is kept whole apart from separator normalization).
|
|
7
|
+
*
|
|
8
|
+
* Replaces the old `replaceAll(commonRoot, '.')` pattern, which corrupted
|
|
9
|
+
* paths when the root substring recurred mid-path or collapsed to ''.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toPortableRelativePath(absolutePath: string, commonRoot: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toPortableRelativePath = toPortableRelativePath;
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
/**
|
|
6
|
+
* Convert an absolute path into the portable './'-prefixed, forward-slash
|
|
7
|
+
* relative form used as flow keys across submission, metadata maps, and
|
|
8
|
+
* polling. `commonRoot` must be a whole-segment prefix of `absolutePath`
|
|
9
|
+
* without a trailing separator (or '' when no common root exists, in which
|
|
10
|
+
* case the path is kept whole apart from separator normalization).
|
|
11
|
+
*
|
|
12
|
+
* Replaces the old `replaceAll(commonRoot, '.')` pattern, which corrupted
|
|
13
|
+
* paths when the root substring recurred mid-path or collapsed to ''.
|
|
14
|
+
*/
|
|
15
|
+
function toPortableRelativePath(absolutePath, commonRoot) {
|
|
16
|
+
let relative = absolutePath;
|
|
17
|
+
if (commonRoot && absolutePath.startsWith(commonRoot)) {
|
|
18
|
+
relative = absolutePath.slice(commonRoot.length);
|
|
19
|
+
}
|
|
20
|
+
if (!relative.startsWith(path.sep)) {
|
|
21
|
+
relative = path.sep + relative;
|
|
22
|
+
}
|
|
23
|
+
return ('.' + relative).split(path.sep).join('/');
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare class Action {
|
|
2
|
+
private current;
|
|
3
|
+
private _status;
|
|
4
|
+
start(title: string, initialStatus?: string, _opts?: unknown): void;
|
|
5
|
+
stop(message?: string): void;
|
|
6
|
+
set status(value: string);
|
|
7
|
+
get status(): string;
|
|
8
|
+
}
|
|
9
|
+
export declare const ux: {
|
|
10
|
+
action: Action;
|
|
11
|
+
info(message: string): void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ux = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Progress adapter that wraps @clack/prompts spinner with a
|
|
6
|
+
* drop-in API for existing services that used oclif's `ux.action` / `ux.info`.
|
|
7
|
+
*
|
|
8
|
+
* Keeps call sites unchanged while migrating away from @oclif/core.
|
|
9
|
+
*/
|
|
10
|
+
const p = require("@clack/prompts");
|
|
11
|
+
class Action {
|
|
12
|
+
current = null;
|
|
13
|
+
_status = '';
|
|
14
|
+
start(title, initialStatus, _opts) {
|
|
15
|
+
if (this.current) {
|
|
16
|
+
this.current.stop();
|
|
17
|
+
}
|
|
18
|
+
this.current = p.spinner();
|
|
19
|
+
this._status = initialStatus ?? '';
|
|
20
|
+
this.current.start(initialStatus ? `${title} — ${initialStatus}` : title);
|
|
21
|
+
}
|
|
22
|
+
stop(message) {
|
|
23
|
+
if (!this.current) {
|
|
24
|
+
if (message) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.log(message);
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.current.stop(message ?? '');
|
|
31
|
+
this.current = null;
|
|
32
|
+
this._status = '';
|
|
33
|
+
}
|
|
34
|
+
set status(value) {
|
|
35
|
+
this._status = value;
|
|
36
|
+
if (this.current && value) {
|
|
37
|
+
this.current.message(value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
get status() {
|
|
41
|
+
return this._status;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.ux = {
|
|
45
|
+
action: new Action(),
|
|
46
|
+
info(message) {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.log(message);
|
|
49
|
+
},
|
|
50
|
+
};
|
package/dist/utils/styling.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import chalk = require('chalk');
|
|
2
|
-
/**
|
|
3
|
-
* Centralized styling utilities for CLI output
|
|
4
|
-
* Provides consistent, developer-friendly visual formatting
|
|
5
|
-
*/
|
|
6
2
|
/**
|
|
7
3
|
* Status symbols with associated colors
|
|
8
4
|
*/
|
|
@@ -97,9 +93,21 @@ export declare function formatTestSummary(summary: {
|
|
|
97
93
|
* @returns Formatted box with borders
|
|
98
94
|
*/
|
|
99
95
|
export declare function box(content: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Minimal column table renderer.
|
|
98
|
+
* Columns are defined with a `get(row) => string` accessor and optional header label.
|
|
99
|
+
* Matches the subset of oclif's ux.table used by this CLI.
|
|
100
|
+
*/
|
|
101
|
+
export declare function table<T>(rows: T[], columns: Record<string, {
|
|
102
|
+
get: (row: T) => string;
|
|
103
|
+
header?: string;
|
|
104
|
+
}>, options?: {
|
|
105
|
+
printLine?: (line: string) => void;
|
|
106
|
+
}): void;
|
|
100
107
|
/**
|
|
101
108
|
* Generate console URL based on API URL
|
|
102
|
-
*
|
|
109
|
+
* Derives the console host from the known environment matching the API URL;
|
|
110
|
+
* unknown API URLs fall back to the dev console (historical behavior).
|
|
103
111
|
* @param apiUrl - The API URL being used
|
|
104
112
|
* @param uploadId - The upload ID
|
|
105
113
|
* @param resultId - The result ID
|
package/dist/utils/styling.js
CHANGED
|
@@ -9,12 +9,17 @@ exports.formatUrl = formatUrl;
|
|
|
9
9
|
exports.formatId = formatId;
|
|
10
10
|
exports.formatTestSummary = formatTestSummary;
|
|
11
11
|
exports.box = box;
|
|
12
|
+
exports.table = table;
|
|
12
13
|
exports.getConsoleUrl = getConsoleUrl;
|
|
13
14
|
const chalk = require("chalk");
|
|
15
|
+
const environments_1 = require("../config/environments");
|
|
14
16
|
/**
|
|
15
17
|
* Centralized styling utilities for CLI output
|
|
16
18
|
* Provides consistent, developer-friendly visual formatting
|
|
17
19
|
*/
|
|
20
|
+
/** Strip ANSI color escape sequences for visible-width calculations. */
|
|
21
|
+
// eslint-disable-next-line no-control-regex -- matches ANSI escape sequences
|
|
22
|
+
const stripAnsi = (s) => s.replace(/\u001B\[[0-9;]*m/g, '');
|
|
18
23
|
/**
|
|
19
24
|
* Status symbols with associated colors
|
|
20
25
|
*/
|
|
@@ -147,25 +152,50 @@ function formatTestSummary(summary) {
|
|
|
147
152
|
*/
|
|
148
153
|
function box(content) {
|
|
149
154
|
const lines = content.split('\n');
|
|
150
|
-
const
|
|
155
|
+
const visibleLen = (s) => stripAnsi(s).length;
|
|
156
|
+
const maxLength = Math.max(...lines.map((l) => visibleLen(l)));
|
|
151
157
|
const top = chalk.gray('┌' + '─'.repeat(maxLength + 2) + '┐');
|
|
152
158
|
const bottom = chalk.gray('└' + '─'.repeat(maxLength + 2) + '┘');
|
|
153
159
|
const middle = lines
|
|
154
|
-
.map((line) => chalk.gray('│ ') + line.
|
|
160
|
+
.map((line) => chalk.gray('│ ') + line + ' '.repeat(Math.max(0, maxLength - visibleLen(line))) + chalk.gray(' │'))
|
|
155
161
|
.join('\n');
|
|
156
162
|
return `${top}\n${middle}\n${bottom}`;
|
|
157
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Minimal column table renderer.
|
|
166
|
+
* Columns are defined with a `get(row) => string` accessor and optional header label.
|
|
167
|
+
* Matches the subset of oclif's ux.table used by this CLI.
|
|
168
|
+
*/
|
|
169
|
+
function table(rows, columns, options = {}) {
|
|
170
|
+
const printLine = options.printLine ?? ((line) => {
|
|
171
|
+
// eslint-disable-next-line no-console
|
|
172
|
+
console.log(line);
|
|
173
|
+
});
|
|
174
|
+
const keys = Object.keys(columns);
|
|
175
|
+
const headers = keys.map((k) => columns[k].header ?? k);
|
|
176
|
+
const cells = rows.map((row) => keys.map((k) => String(columns[k].get(row) ?? '')));
|
|
177
|
+
const widths = keys.map((_, i) => Math.max(stripAnsi(headers[i]).length, ...cells.map((r) => stripAnsi(r[i]).length)));
|
|
178
|
+
const pad = (s, width) => {
|
|
179
|
+
const visibleLen = stripAnsi(s).length;
|
|
180
|
+
return s + ' '.repeat(Math.max(0, width - visibleLen));
|
|
181
|
+
};
|
|
182
|
+
printLine(headers.map((h, i) => pad(chalk.bold(h), widths[i])).join(' '));
|
|
183
|
+
printLine(widths.map((w) => chalk.gray('─'.repeat(w))).join(' '));
|
|
184
|
+
for (const row of cells) {
|
|
185
|
+
printLine(row.map((c, i) => pad(c, widths[i])).join(' '));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
158
188
|
/**
|
|
159
189
|
* Generate console URL based on API URL
|
|
160
|
-
*
|
|
190
|
+
* Derives the console host from the known environment matching the API URL;
|
|
191
|
+
* unknown API URLs fall back to the dev console (historical behavior).
|
|
161
192
|
* @param apiUrl - The API URL being used
|
|
162
193
|
* @param uploadId - The upload ID
|
|
163
194
|
* @param resultId - The result ID
|
|
164
195
|
* @returns The appropriate console URL
|
|
165
196
|
*/
|
|
166
197
|
function getConsoleUrl(apiUrl, uploadId, resultId) {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
return `https://${consoleSubdomain}.devicecloud.dev/results?upload=${uploadId}&result=${resultId}`;
|
|
198
|
+
const env = (0, environments_1.findEnvByApiUrl)(apiUrl);
|
|
199
|
+
const base = env?.frontendUrl ?? 'https://dev.console.devicecloud.dev';
|
|
200
|
+
return `${base}/results?upload=${uploadId}&result=${resultId}`;
|
|
171
201
|
}
|
package/package.json
CHANGED
|
@@ -1,66 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "devicecloud.dev",
|
|
3
3
|
"bin": {
|
|
4
|
-
"dcd": "
|
|
4
|
+
"dcd": "dist/index.js"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@
|
|
8
|
-
"@oclif/plugin-help": "^6.2.37",
|
|
7
|
+
"@clack/prompts": "^1.2.0",
|
|
9
8
|
"@supabase/supabase-js": "^2.99.1",
|
|
10
|
-
"app-info-parser": "^1.1.6",
|
|
11
|
-
"archiver": "^7.0.1",
|
|
12
9
|
"bplist-parser": "^0.3.2",
|
|
13
10
|
"chalk": "4.1.2",
|
|
14
|
-
"
|
|
11
|
+
"citty": "^0.1.6",
|
|
15
12
|
"js-yaml": "^4.1.1",
|
|
13
|
+
"node-apk": "^1.2.1",
|
|
16
14
|
"node-stream-zip": "^1.15.0",
|
|
17
15
|
"plist": "^3.1.0",
|
|
18
16
|
"tar": "^7.5.11",
|
|
19
|
-
"tus-js-client": "^4.3.1"
|
|
17
|
+
"tus-js-client": "^4.3.1",
|
|
18
|
+
"yazl": "^3.3.1"
|
|
20
19
|
},
|
|
21
20
|
"description": "Better cloud maestro testing",
|
|
22
21
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"@
|
|
25
|
-
"@types/archiver": "^7.0.0",
|
|
26
|
-
"@types/chai": "^5.2.3",
|
|
27
|
-
"@types/glob-to-regexp": "^0.4.4",
|
|
22
|
+
"@eslint/js": "^9.39.4",
|
|
23
|
+
"@types/chai": "^4.3.20",
|
|
28
24
|
"@types/js-yaml": "^4.0.9",
|
|
29
25
|
"@types/mocha": "^10.0.10",
|
|
30
26
|
"@types/node": "^25.4.0",
|
|
31
27
|
"@types/plist": "^3.0.5",
|
|
32
|
-
"@types/
|
|
33
|
-
"chai": "^
|
|
34
|
-
"eslint": "^
|
|
35
|
-
"eslint-config-oclif": "^5.2.2",
|
|
36
|
-
"eslint-config-oclif-typescript": "^3.1.14",
|
|
28
|
+
"@types/yazl": "^3.3.1",
|
|
29
|
+
"chai": "^4.5.0",
|
|
30
|
+
"eslint": "^9.16.0",
|
|
37
31
|
"eslint-config-prettier": "^10.1.8",
|
|
32
|
+
"eslint-plugin-import": "^2.32.0",
|
|
33
|
+
"eslint-plugin-unicorn": "^64.0.0",
|
|
34
|
+
"husky": "^9.1.7",
|
|
38
35
|
"mocha": "^11.7.5",
|
|
39
|
-
"
|
|
36
|
+
"prettier": "^3.3.3",
|
|
40
37
|
"shx": "^0.4.0",
|
|
41
|
-
"
|
|
42
|
-
"typescript": "^5.9.3"
|
|
38
|
+
"tsx": "^4.19.2",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"typescript-eslint": "^8.59.0"
|
|
43
41
|
},
|
|
44
42
|
"engines": {
|
|
45
43
|
"node": ">=22.0.0"
|
|
46
44
|
},
|
|
47
45
|
"files": [
|
|
48
|
-
"/
|
|
49
|
-
"/dist",
|
|
50
|
-
"/oclif.manifest.json"
|
|
46
|
+
"/dist"
|
|
51
47
|
],
|
|
52
48
|
"homepage": "https://devicecloud.dev",
|
|
53
49
|
"license": "MIT",
|
|
54
50
|
"main": "dist/index.js",
|
|
55
51
|
"name": "@devicecloud.dev/dcd",
|
|
56
|
-
"oclif": {
|
|
57
|
-
"bin": "dcd",
|
|
58
|
-
"dirname": "dcd",
|
|
59
|
-
"commands": "./dist/commands",
|
|
60
|
-
"plugins": [
|
|
61
|
-
"@oclif/plugin-help"
|
|
62
|
-
]
|
|
63
|
-
},
|
|
64
52
|
"private": false,
|
|
65
53
|
"publishConfig": {
|
|
66
54
|
"access": "public"
|
|
@@ -69,7 +57,7 @@
|
|
|
69
57
|
"type": "git",
|
|
70
58
|
"url": "https://devicecloud.dev"
|
|
71
59
|
},
|
|
72
|
-
"version": "
|
|
60
|
+
"version": "5.0.0-beta.0",
|
|
73
61
|
"bugs": {
|
|
74
62
|
"url": "https://discord.gg/gm3mJwcNw8"
|
|
75
63
|
},
|
|
@@ -83,11 +71,11 @@
|
|
|
83
71
|
],
|
|
84
72
|
"types": "dist/index.d.ts",
|
|
85
73
|
"scripts": {
|
|
86
|
-
"dcd": "
|
|
87
|
-
"
|
|
88
|
-
"build": "
|
|
89
|
-
"lint": "eslint
|
|
90
|
-
"
|
|
91
|
-
"
|
|
74
|
+
"dcd": "tsx src/index.ts",
|
|
75
|
+
"build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js",
|
|
76
|
+
"build:binaries": "node scripts/build-binaries.mjs",
|
|
77
|
+
"lint": "eslint src test --ext .ts",
|
|
78
|
+
"test": "node scripts/test-runner.mjs",
|
|
79
|
+
"typecheck": "tsc --noEmit -p tsconfig.test.json"
|
|
92
80
|
}
|
|
93
81
|
}
|
package/bin/dev.cmd
DELETED
package/bin/dev.js
DELETED
package/bin/run.cmd
DELETED