vibeview 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-46X7UN6Y.js → chunk-7IOGMM5W.js} +92 -45
- package/dist/{chunk-U7FCGMWG.js → chunk-FTTWLCTE.js} +46 -2
- package/dist/cli.js +51 -15
- package/dist/{server-XJREB7KU.js → server-ZIUZ7U5T.js} +3 -3
- package/dist/{upload-app-QUUXKNR2.js → upload-app-MDKTSG4O.js} +1 -1
- package/package.json +1 -1
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
UsageError,
|
|
7
7
|
createSpinner,
|
|
8
8
|
errorExitCode,
|
|
9
|
+
formatUtcTimestamp,
|
|
9
10
|
loadConfig,
|
|
11
|
+
queueReasonCopy,
|
|
10
12
|
resolveAuth,
|
|
11
13
|
uploadAppBuild
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FTTWLCTE.js";
|
|
13
15
|
|
|
14
16
|
// src/lib/dev-config.ts
|
|
15
17
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -249,17 +251,6 @@ function checkDevBuild(app, buildId, platform) {
|
|
|
249
251
|
|
|
250
252
|
// src/commands/list-builds.ts
|
|
251
253
|
import chalk from "chalk";
|
|
252
|
-
|
|
253
|
-
// src/lib/formatters/time.ts
|
|
254
|
-
function formatUtcTimestamp(iso) {
|
|
255
|
-
if (!iso) return "";
|
|
256
|
-
const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(iso);
|
|
257
|
-
const d = new Date(hasZone ? iso : `${iso}Z`);
|
|
258
|
-
if (Number.isNaN(d.getTime())) return iso;
|
|
259
|
-
return `${d.toISOString().slice(0, 19).replace("T", " ")} UTC`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// src/commands/list-builds.ts
|
|
263
254
|
function buildRows(app) {
|
|
264
255
|
return app.builds.map((b) => ({
|
|
265
256
|
id: b.id,
|
|
@@ -2196,6 +2187,11 @@ function parseWatchSeconds(flag) {
|
|
|
2196
2187
|
}
|
|
2197
2188
|
return seconds;
|
|
2198
2189
|
}
|
|
2190
|
+
function queuedLine(position, reasonMessage) {
|
|
2191
|
+
const where = position ? ` (position ${position})` : "";
|
|
2192
|
+
const why = reasonMessage ?? "Every device is busy.";
|
|
2193
|
+
return `\u23F3 ${why} Waiting in the queue${where}, Ctrl-C to give up.`;
|
|
2194
|
+
}
|
|
2199
2195
|
function emit(json, evt) {
|
|
2200
2196
|
if (json) console.log(JSON.stringify(evt));
|
|
2201
2197
|
}
|
|
@@ -2232,7 +2228,9 @@ async function startDevSession(cfg, onEvent = () => {
|
|
|
2232
2228
|
event: "queued",
|
|
2233
2229
|
session_id: sessionId,
|
|
2234
2230
|
position: started.position ?? null,
|
|
2235
|
-
queue_entry_id: queueEntryId
|
|
2231
|
+
queue_entry_id: queueEntryId,
|
|
2232
|
+
queue_reason: started.queue_reason ?? null,
|
|
2233
|
+
message: queueReasonCopy(started.queue_reason)
|
|
2236
2234
|
});
|
|
2237
2235
|
await pollWhileQueued(api, sessionId, QUEUE_WAIT_MS);
|
|
2238
2236
|
queueEntryId = null;
|
|
@@ -2323,6 +2321,10 @@ function findBlockingWarning(info) {
|
|
|
2323
2321
|
return (info.warnings ?? []).find((w) => BLOCKING_WARNING_PREFIXES.some((p) => w.includes(p)));
|
|
2324
2322
|
}
|
|
2325
2323
|
var QUEUE_WAIT_MS = 30 * 6e4;
|
|
2324
|
+
function sessionEndedMessage(info, when) {
|
|
2325
|
+
const reason = info.status === "failed" && info.failure_reason ? `: ${info.failure_reason}` : "";
|
|
2326
|
+
return `session ${info.status} ${when}${reason}`;
|
|
2327
|
+
}
|
|
2326
2328
|
async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
|
|
2327
2329
|
const deadline = Date.now() + timeoutMs;
|
|
2328
2330
|
for (; ; ) {
|
|
@@ -2330,7 +2332,7 @@ async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
|
|
|
2330
2332
|
const blocking = findBlockingWarning(info);
|
|
2331
2333
|
if (blocking) throw new Error(blocking);
|
|
2332
2334
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2333
|
-
throw new Error(
|
|
2335
|
+
throw new Error(sessionEndedMessage(info, "while waiting for a device"));
|
|
2334
2336
|
}
|
|
2335
2337
|
if (info.status !== "pending" && info.status !== "queued") return info;
|
|
2336
2338
|
if (Date.now() > deadline) {
|
|
@@ -2349,7 +2351,7 @@ async function pollUntilTunnelReady(api, sessionId, timeoutMs, intervalMs = 2e3)
|
|
|
2349
2351
|
if (blocking) throw new Error(blocking);
|
|
2350
2352
|
if (info.tunnel_url) return info;
|
|
2351
2353
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2352
|
-
throw new Error(
|
|
2354
|
+
throw new Error(sessionEndedMessage(info, "before the tunnel came up"));
|
|
2353
2355
|
}
|
|
2354
2356
|
if (Date.now() > deadline) throw new Error("timed out waiting for the device to start");
|
|
2355
2357
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
@@ -2368,7 +2370,7 @@ async function pollUntilActive(api, sessionId, timeoutMs, intervalMs = 2e3, onIn
|
|
|
2368
2370
|
}
|
|
2369
2371
|
if (info.status === "active" && !info.app_install_pending) return info;
|
|
2370
2372
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2371
|
-
throw new Error(
|
|
2373
|
+
throw new Error(sessionEndedMessage(info, "before it became active"));
|
|
2372
2374
|
}
|
|
2373
2375
|
if (Date.now() > deadline) {
|
|
2374
2376
|
throw new Error("timed out waiting for the session to become active");
|
|
@@ -2418,6 +2420,19 @@ function validateAppChoice(requested, fromFlag, apps) {
|
|
|
2418
2420
|
if (apps.some((a) => a.public_id === requested)) return { appId: requested };
|
|
2419
2421
|
return fromFlag ? { staleFlag: requested } : { staleCached: requested };
|
|
2420
2422
|
}
|
|
2423
|
+
function staleAppFlagError(requested, platform, allApps) {
|
|
2424
|
+
const other = allApps.find((a) => a.public_id === requested);
|
|
2425
|
+
if (other) {
|
|
2426
|
+
return {
|
|
2427
|
+
message: `app "${requested}" is ${platformWithArticle(other.platform)} app, not ${platformWithArticle(platform)} app.`,
|
|
2428
|
+
hint: `Run \`vibeview dev --platform ${other.platform} --app ${requested}\`, or \`vibeview dev --platform ${platform}\` without --app to pick ${platformWithArticle(platform)} app.`
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
return {
|
|
2432
|
+
message: `app "${requested}" not found \u2014 it may have been deleted.`,
|
|
2433
|
+
hint: "Run `vibeview dev` without --app to pick from the list."
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2421
2436
|
function buildAppStrategy(apps, buildFlag, json, isTTY) {
|
|
2422
2437
|
if (buildFlag) return "build";
|
|
2423
2438
|
if (apps.length === 0) return "build";
|
|
@@ -2495,11 +2510,9 @@ async function ensureConfig(api, opts) {
|
|
|
2495
2510
|
const apps = appsForPlatform(allApps, platform);
|
|
2496
2511
|
const choice = validateAppChoice(opts.app || entry?.appId, Boolean(opts.app), apps);
|
|
2497
2512
|
if (choice.staleFlag) {
|
|
2498
|
-
const message =
|
|
2499
|
-
console.error(
|
|
2500
|
-
|
|
2501
|
-
Run \`vibeview dev\` without --app to pick from the list.`
|
|
2502
|
-
);
|
|
2513
|
+
const { message, hint } = staleAppFlagError(choice.staleFlag, platform, allApps);
|
|
2514
|
+
console.error(chalk4.red(`error: ${message}`) + `
|
|
2515
|
+
${hint}`);
|
|
2503
2516
|
emit(opts.json, { event: "error", message });
|
|
2504
2517
|
process.exit(2);
|
|
2505
2518
|
}
|
|
@@ -2806,12 +2819,7 @@ async function runDevInProcess(opts) {
|
|
|
2806
2819
|
console.error(chalk4.yellow(`\u26A0 ${evt.message}`));
|
|
2807
2820
|
}
|
|
2808
2821
|
} else if (evt.event === "queued") {
|
|
2809
|
-
|
|
2810
|
-
console.error(
|
|
2811
|
-
chalk4.yellow(
|
|
2812
|
-
`\u23F3 every device is busy \u2014 waiting in the queue${where}, Ctrl-C to give up`
|
|
2813
|
-
)
|
|
2814
|
-
);
|
|
2822
|
+
console.error(chalk4.yellow(queuedLine(evt.position, evt.message)));
|
|
2815
2823
|
} else if (evt.event === "tunnel_connected") {
|
|
2816
2824
|
everConnected = true;
|
|
2817
2825
|
console.error(chalk4.green("\u2713 tunnel connected \u2014 edit away, Fast Refresh is live"));
|
|
@@ -3056,7 +3064,7 @@ function formatDevicesHuman(devices) {
|
|
|
3056
3064
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
3057
3065
|
const lines = [chalk5.bold(fmt(HEADERS2))];
|
|
3058
3066
|
for (const row of rows.sort(rowSort)) {
|
|
3059
|
-
const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
|
|
3067
|
+
const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" || row[5] === "updating" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
|
|
3060
3068
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
3061
3069
|
}
|
|
3062
3070
|
return lines.join("\n") + "\n";
|
|
@@ -3087,25 +3095,65 @@ function formatDeviceModelsHuman(models) {
|
|
|
3087
3095
|
}
|
|
3088
3096
|
|
|
3089
3097
|
// src/agent/registry.ts
|
|
3090
|
-
var
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
[
|
|
3094
|
-
"
|
|
3095
|
-
|
|
3098
|
+
var PRESS_BUTTONS = {
|
|
3099
|
+
ios: ["home", "back", "lock", "siri", "enter"],
|
|
3100
|
+
android: ["home", "back", "lock", "enter"],
|
|
3101
|
+
appleTv: [
|
|
3102
|
+
"dpad_up",
|
|
3103
|
+
"dpad_down",
|
|
3104
|
+
"dpad_left",
|
|
3105
|
+
"dpad_right",
|
|
3106
|
+
"dpad_center",
|
|
3107
|
+
"back",
|
|
3108
|
+
"home",
|
|
3109
|
+
"menu"
|
|
3110
|
+
],
|
|
3111
|
+
androidTv: [
|
|
3112
|
+
"dpad_up",
|
|
3113
|
+
"dpad_down",
|
|
3114
|
+
"dpad_left",
|
|
3115
|
+
"dpad_right",
|
|
3116
|
+
"dpad_center",
|
|
3117
|
+
"back",
|
|
3118
|
+
"home",
|
|
3119
|
+
"enter"
|
|
3096
3120
|
],
|
|
3097
|
-
[
|
|
3098
|
-
"
|
|
3099
|
-
|
|
3121
|
+
roku: [
|
|
3122
|
+
"dpad_up",
|
|
3123
|
+
"dpad_down",
|
|
3124
|
+
"dpad_left",
|
|
3125
|
+
"dpad_right",
|
|
3126
|
+
"dpad_center",
|
|
3127
|
+
"back",
|
|
3128
|
+
"enter",
|
|
3129
|
+
"up",
|
|
3130
|
+
"down",
|
|
3131
|
+
"left",
|
|
3132
|
+
"right",
|
|
3133
|
+
"select",
|
|
3134
|
+
"backspace",
|
|
3135
|
+
"play",
|
|
3136
|
+
"rewind",
|
|
3137
|
+
"forward",
|
|
3138
|
+
"replay",
|
|
3139
|
+
"info"
|
|
3100
3140
|
]
|
|
3141
|
+
};
|
|
3142
|
+
var PRESS_PLATFORM_LABELS = [
|
|
3143
|
+
["ios", "iPhone / iPad"],
|
|
3144
|
+
["android", "Android phone / tablet"],
|
|
3145
|
+
["appleTv", "Apple TV"],
|
|
3146
|
+
["androidTv", "Android TV"],
|
|
3147
|
+
["roku", "Roku (beta)"]
|
|
3101
3148
|
];
|
|
3149
|
+
var PRESS_BUTTONS_BY_PLATFORM = PRESS_PLATFORM_LABELS.map(([key, label]) => [label, PRESS_BUTTONS[key]]);
|
|
3150
|
+
function pressButtonLines(indent) {
|
|
3151
|
+
return PRESS_BUTTONS_BY_PLATFORM.map(
|
|
3152
|
+
([platform, buttons]) => `${indent}${platform}: ${buttons.join(", ")}`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3102
3155
|
function pressButtonsHelp() {
|
|
3103
|
-
return [
|
|
3104
|
-
"Valid buttons:",
|
|
3105
|
-
...PRESS_BUTTONS_BY_PLATFORM.map(
|
|
3106
|
-
([platform, buttons]) => ` ${platform}: ${buttons.join(", ")}`
|
|
3107
|
-
)
|
|
3108
|
-
].join("\n");
|
|
3156
|
+
return ["Valid buttons:", ...pressButtonLines(" ")].join("\n");
|
|
3109
3157
|
}
|
|
3110
3158
|
var AGENT_COMMANDS = [
|
|
3111
3159
|
{
|
|
@@ -3401,7 +3449,7 @@ var AGENT_COMMANDS = [
|
|
|
3401
3449
|
cli: "positional",
|
|
3402
3450
|
type: "string",
|
|
3403
3451
|
required: true,
|
|
3404
|
-
description:
|
|
3452
|
+
description: `Button to press. ${pressButtonLines("").join(". ")}.`
|
|
3405
3453
|
}
|
|
3406
3454
|
]
|
|
3407
3455
|
},
|
|
@@ -3883,7 +3931,6 @@ export {
|
|
|
3883
3931
|
loadDevConfig,
|
|
3884
3932
|
parseBuildId,
|
|
3885
3933
|
checkDevBuild,
|
|
3886
|
-
formatUtcTimestamp,
|
|
3887
3934
|
buildRows,
|
|
3888
3935
|
formatBuildRowsHuman,
|
|
3889
3936
|
listBuilds,
|
|
@@ -42,6 +42,32 @@ import { createWriteStream, mkdirSync as mkdirSync2, renameSync, rmSync } from "
|
|
|
42
42
|
import { basename, join as join2, resolve, sep } from "path";
|
|
43
43
|
import { pipeline } from "stream/promises";
|
|
44
44
|
import { Readable } from "stream";
|
|
45
|
+
|
|
46
|
+
// src/lib/formatters/time.ts
|
|
47
|
+
function formatUtcTimestamp(iso) {
|
|
48
|
+
if (!iso) return "";
|
|
49
|
+
const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(iso);
|
|
50
|
+
const d = new Date(hasZone ? iso : `${iso}Z`);
|
|
51
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
52
|
+
return `${d.toISOString().slice(0, 19).replace("T", " ")} UTC`;
|
|
53
|
+
}
|
|
54
|
+
var NAIVE_ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
55
|
+
function withUtcTimestamps(value) {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
return NAIVE_ISO_DATETIME.test(value) ? `${value}Z` : value;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) return value.map((v) => withUtcTimestamps(v));
|
|
60
|
+
if (value !== null && typeof value === "object") {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of Object.entries(value)) {
|
|
63
|
+
out[k] = withUtcTimestamps(v);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/api-client.ts
|
|
45
71
|
var AuthError = class extends Error {
|
|
46
72
|
constructor(message = "Authentication failed. Check your API token.") {
|
|
47
73
|
super(message);
|
|
@@ -53,10 +79,14 @@ function errorExitCode(err, otherwise = 1) {
|
|
|
53
79
|
}
|
|
54
80
|
function reportCommandError(json, err) {
|
|
55
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
-
if (json)
|
|
82
|
+
if (json) printJsonError(err);
|
|
57
83
|
process.stderr.write(`error: ${message}
|
|
58
84
|
`);
|
|
59
85
|
}
|
|
86
|
+
function printJsonError(err) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
console.log(JSON.stringify({ event: "error", message }));
|
|
89
|
+
}
|
|
60
90
|
var NotFoundError = class extends Error {
|
|
61
91
|
constructor(message) {
|
|
62
92
|
super(message);
|
|
@@ -75,7 +105,7 @@ function extractDetail(text) {
|
|
|
75
105
|
}
|
|
76
106
|
async function parseJsonBody(resp) {
|
|
77
107
|
if (resp.status === 204 || resp.status === 205) return void 0;
|
|
78
|
-
return resp.json();
|
|
108
|
+
return withUtcTimestamps(await resp.json());
|
|
79
109
|
}
|
|
80
110
|
var ApiClient = class {
|
|
81
111
|
constructor(baseUrl, token) {
|
|
@@ -663,6 +693,16 @@ var UsageError = class extends Error {
|
|
|
663
693
|
this.name = "UsageError";
|
|
664
694
|
}
|
|
665
695
|
};
|
|
696
|
+
var DEVICE_STATUS_FILTERS = ["available", "busy", "updating"];
|
|
697
|
+
var QUEUE_REASON_COPY = {
|
|
698
|
+
devices_busy: "All devices are in use right now.",
|
|
699
|
+
devices_updating: "Devices are being updated and will be back shortly.",
|
|
700
|
+
plan_limit: "The most sessions this app can run at once are already running.",
|
|
701
|
+
link_limit: "The most people who can play at once are already playing."
|
|
702
|
+
};
|
|
703
|
+
function queueReasonCopy(reason) {
|
|
704
|
+
return reason ? QUEUE_REASON_COPY[reason] ?? null : null;
|
|
705
|
+
}
|
|
666
706
|
|
|
667
707
|
// src/commands/upload-app.ts
|
|
668
708
|
async function uploadAppBuild(filePath, options) {
|
|
@@ -746,9 +786,11 @@ export {
|
|
|
746
786
|
loadConfig,
|
|
747
787
|
saveConfig,
|
|
748
788
|
resolveAuth,
|
|
789
|
+
formatUtcTimestamp,
|
|
749
790
|
AuthError,
|
|
750
791
|
errorExitCode,
|
|
751
792
|
reportCommandError,
|
|
793
|
+
printJsonError,
|
|
752
794
|
NotFoundError,
|
|
753
795
|
ApiClient,
|
|
754
796
|
createSpinner,
|
|
@@ -756,6 +798,8 @@ export {
|
|
|
756
798
|
EXIT_TEST_FAIL,
|
|
757
799
|
EXIT_INFRA_ERROR,
|
|
758
800
|
UsageError,
|
|
801
|
+
DEVICE_STATUS_FILTERS,
|
|
802
|
+
queueReasonCopy,
|
|
759
803
|
uploadAppBuild,
|
|
760
804
|
describeBuild,
|
|
761
805
|
uploadApp
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
formatDeviceModelsHuman,
|
|
12
12
|
formatDevicesHuman,
|
|
13
13
|
formatDuration,
|
|
14
|
-
formatUtcTimestamp,
|
|
15
14
|
killOwnedMetro,
|
|
16
15
|
listBuilds,
|
|
17
16
|
loadDevConfig,
|
|
@@ -23,9 +22,10 @@ import {
|
|
|
23
22
|
readDevState,
|
|
24
23
|
readMetroState,
|
|
25
24
|
runAgentVerb
|
|
26
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-7IOGMM5W.js";
|
|
27
26
|
import {
|
|
28
27
|
ApiClient,
|
|
28
|
+
DEVICE_STATUS_FILTERS,
|
|
29
29
|
EXIT_INFRA_ERROR,
|
|
30
30
|
EXIT_PASS,
|
|
31
31
|
EXIT_TEST_FAIL,
|
|
@@ -33,12 +33,14 @@ import {
|
|
|
33
33
|
UsageError,
|
|
34
34
|
createSpinner,
|
|
35
35
|
errorExitCode,
|
|
36
|
+
formatUtcTimestamp,
|
|
36
37
|
loadConfig,
|
|
38
|
+
printJsonError,
|
|
37
39
|
reportCommandError,
|
|
38
40
|
resolveAuth,
|
|
39
41
|
saveConfig,
|
|
40
42
|
uploadApp
|
|
41
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-FTTWLCTE.js";
|
|
42
44
|
|
|
43
45
|
// src/cli.ts
|
|
44
46
|
import { createRequire } from "module";
|
|
@@ -52,6 +54,26 @@ import chalk2 from "chalk";
|
|
|
52
54
|
|
|
53
55
|
// src/lib/formatters/human.ts
|
|
54
56
|
import chalk from "chalk";
|
|
57
|
+
|
|
58
|
+
// src/lib/formatters/cases.ts
|
|
59
|
+
function resultCases(result) {
|
|
60
|
+
if (Array.isArray(result.test_runs)) return result.test_runs;
|
|
61
|
+
const failedStep = result.steps?.find((s) => s.status !== "passed");
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
case_name: result.case_name || result.run_id,
|
|
65
|
+
status: result.status,
|
|
66
|
+
duration_ms: result.duration_ms,
|
|
67
|
+
error_message: result.error_message ?? failedStep?.error ?? null,
|
|
68
|
+
error_kind: failedStep?.error_kind ?? null,
|
|
69
|
+
error_kind_description: failedStep?.error_kind_description ?? null,
|
|
70
|
+
screenshot_signed_url: failedStep?.screenshot_signed_url ?? null,
|
|
71
|
+
url: result.url
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/lib/formatters/human.ts
|
|
55
77
|
function failureLines(failure) {
|
|
56
78
|
const lines = [];
|
|
57
79
|
if (failure.error_kind) {
|
|
@@ -77,15 +99,17 @@ function formatHuman(result, verbose) {
|
|
|
77
99
|
} else {
|
|
78
100
|
lines.push(` ${icon} ${chalk.bold(result.run_id)} ${chalk.dim(duration)}`);
|
|
79
101
|
}
|
|
80
|
-
const
|
|
81
|
-
const
|
|
102
|
+
const cases = resultCases(result);
|
|
103
|
+
const passed = cases.filter((t) => t.status === "passed").length;
|
|
104
|
+
const total = cases.length;
|
|
82
105
|
lines.push(` ${chalk.dim(`${passed}/${total} cases passed`)}`);
|
|
83
106
|
if (result.url) {
|
|
84
107
|
lines.push(` ${chalk.dim(result.url)}`);
|
|
85
108
|
}
|
|
86
|
-
|
|
109
|
+
const suiteCases = result.test_runs ?? [];
|
|
110
|
+
if (suiteCases.length > 0) {
|
|
87
111
|
lines.push("");
|
|
88
|
-
for (const tr of
|
|
112
|
+
for (const tr of suiteCases) {
|
|
89
113
|
const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
|
|
90
114
|
const caseDuration = tr.duration_ms ? `${(tr.duration_ms / 1e3).toFixed(1)}s` : "-";
|
|
91
115
|
lines.push(` ${caseIcon} ${tr.case_name} ${chalk.dim(caseDuration)}`);
|
|
@@ -134,10 +158,11 @@ function escapeXml(s) {
|
|
|
134
158
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
135
159
|
}
|
|
136
160
|
function formatJunit(result) {
|
|
137
|
-
const suiteName = escapeXml(result.suite_name || result.run_id);
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
161
|
+
const suiteName = escapeXml(result.suite_name || result.case_name || result.run_id);
|
|
162
|
+
const cases = resultCases(result);
|
|
163
|
+
const tests = cases.length;
|
|
164
|
+
const failures = cases.filter((t) => t.status === "failed").length;
|
|
165
|
+
const errors = cases.filter((t) => t.status === "error").length;
|
|
141
166
|
const totalTime = result.duration_ms ? (result.duration_ms / 1e3).toFixed(1) : "0";
|
|
142
167
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
143
168
|
const lines = [];
|
|
@@ -148,7 +173,7 @@ function formatJunit(result) {
|
|
|
148
173
|
lines.push(
|
|
149
174
|
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
150
175
|
);
|
|
151
|
-
for (const tr of
|
|
176
|
+
for (const tr of cases) {
|
|
152
177
|
const caseName = escapeXml(tr.case_name);
|
|
153
178
|
const caseTime = tr.duration_ms ? (tr.duration_ms / 1e3).toFixed(1) : "0";
|
|
154
179
|
if (tr.status === "passed") {
|
|
@@ -505,6 +530,7 @@ ${chalk3.dim("Run:")} ${response.run_id}
|
|
|
505
530
|
process.exit(mapExitCode(result.status));
|
|
506
531
|
} catch (err) {
|
|
507
532
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
533
|
+
if (options.output === "json") printJsonError(err);
|
|
508
534
|
if (lastQueueEntryId !== void 0) {
|
|
509
535
|
try {
|
|
510
536
|
await client.cancelQueueEntry(lastQueueEntryId);
|
|
@@ -593,10 +619,13 @@ ${signal} received.
|
|
|
593
619
|
const msg = err instanceof Error ? err.message : String(err);
|
|
594
620
|
if (msg.startsWith("Not found:") || msg.includes("404") || /No suites match tags/.test(msg)) {
|
|
595
621
|
const suffix = opts.platform ? ` on platform ${opts.platform}` : "";
|
|
596
|
-
|
|
622
|
+
const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
|
|
623
|
+
if (opts.output === "json") printJsonError(noMatch);
|
|
624
|
+
process.stderr.write(chalk4.yellow(`${noMatch}
|
|
597
625
|
`));
|
|
598
626
|
process.exit(EXIT_INFRA_ERROR);
|
|
599
627
|
}
|
|
628
|
+
if (opts.output === "json") printJsonError(err);
|
|
600
629
|
process.stderr.write(chalk4.red(`Failed to start: ${msg}
|
|
601
630
|
`));
|
|
602
631
|
process.exit(EXIT_INFRA_ERROR);
|
|
@@ -680,6 +709,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
680
709
|
process.stdin.pause();
|
|
681
710
|
}
|
|
682
711
|
if (!last) {
|
|
712
|
+
if (opts.output === "json") printJsonError("Timed out before any status was returned.");
|
|
683
713
|
process.stderr.write(chalk4.red("Timed out before any status was returned.\n"));
|
|
684
714
|
process.exit(EXIT_INFRA_ERROR);
|
|
685
715
|
}
|
|
@@ -817,6 +847,7 @@ ${chalk5.dim("Run:")} ${response.run_id}
|
|
|
817
847
|
process.exit(mapExitCode(result.status));
|
|
818
848
|
} catch (err) {
|
|
819
849
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
850
|
+
if (options.output === "json") printJsonError(err);
|
|
820
851
|
if (lastQueueEntryId !== void 0) {
|
|
821
852
|
try {
|
|
822
853
|
await client.cancelQueueEntry(lastQueueEntryId);
|
|
@@ -3291,7 +3322,12 @@ program.command("list-devices").description("List devices registered with the pl
|
|
|
3291
3322
|
new Option("--type <type>", "Filter by device type").choices(["ios", "android", "roku"])
|
|
3292
3323
|
).addOption(
|
|
3293
3324
|
new Option("--category <category>", "Filter by category").choices(["phone", "tablet", "tv"])
|
|
3294
|
-
).addOption(
|
|
3325
|
+
).addOption(
|
|
3326
|
+
new Option(
|
|
3327
|
+
"--status <status>",
|
|
3328
|
+
"Filter by status (updating = being updated, takes no new session yet)"
|
|
3329
|
+
).choices([...DEVICE_STATUS_FILTERS])
|
|
3330
|
+
).option(
|
|
3295
3331
|
"--models",
|
|
3296
3332
|
"List the device models you can start instead (the names `dev --model` takes)",
|
|
3297
3333
|
false
|
|
@@ -3465,7 +3501,7 @@ program.command("dev-reload").description(
|
|
|
3465
3501
|
program.command("mcp").description(
|
|
3466
3502
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3467
3503
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3468
|
-
const { runMcpServer } = await import("./server-
|
|
3504
|
+
const { runMcpServer } = await import("./server-ZIUZ7U5T.js");
|
|
3469
3505
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
3470
3506
|
});
|
|
3471
3507
|
program.command("agent-setup").description(
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
readDevState,
|
|
22
22
|
renderEnvelope,
|
|
23
23
|
startDevSession
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7IOGMM5W.js";
|
|
25
25
|
import {
|
|
26
26
|
ApiClient,
|
|
27
27
|
resolveAuth
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FTTWLCTE.js";
|
|
29
29
|
|
|
30
30
|
// src/mcp/server.ts
|
|
31
31
|
import { writeFileSync } from "fs";
|
|
@@ -377,7 +377,7 @@ async function handleUploadApp(args, deps) {
|
|
|
377
377
|
}
|
|
378
378
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
379
379
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
380
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
380
|
+
const { uploadAppBuild } = await import("./upload-app-MDKTSG4O.js");
|
|
381
381
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
382
382
|
return result2;
|
|
383
383
|
});
|