vibeview 0.30.0 → 0.32.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/dist/cli.js
CHANGED
|
@@ -21,9 +21,10 @@ import {
|
|
|
21
21
|
printOutcomeLine,
|
|
22
22
|
printTransition,
|
|
23
23
|
readDevState,
|
|
24
|
+
readDevStateFile,
|
|
24
25
|
readMetroState,
|
|
25
26
|
runAgentVerb
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-T6H5XTBX.js";
|
|
27
28
|
import {
|
|
28
29
|
ApiClient,
|
|
29
30
|
ApiStatusError,
|
|
@@ -51,7 +52,7 @@ import { createRequire } from "module";
|
|
|
51
52
|
import { Command, Option } from "commander";
|
|
52
53
|
|
|
53
54
|
// src/commands/run-suite.ts
|
|
54
|
-
import
|
|
55
|
+
import chalk4 from "chalk";
|
|
55
56
|
|
|
56
57
|
// src/lib/poll.ts
|
|
57
58
|
import chalk2 from "chalk";
|
|
@@ -145,6 +146,10 @@ function formatHuman(result, verbose) {
|
|
|
145
146
|
lines.push(`
|
|
146
147
|
${chalk.red("Error:")} ${result.error_message}`);
|
|
147
148
|
}
|
|
149
|
+
if (result.build_warning && result.status !== "passed") {
|
|
150
|
+
lines.push(`
|
|
151
|
+
${chalk.yellow("Note:")} ${result.build_warning}`);
|
|
152
|
+
}
|
|
148
153
|
lines.push("");
|
|
149
154
|
return lines.join("\n");
|
|
150
155
|
}
|
|
@@ -174,9 +179,16 @@ function formatJunit(result) {
|
|
|
174
179
|
lines.push(
|
|
175
180
|
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
176
181
|
);
|
|
177
|
-
if (result.url) {
|
|
182
|
+
if (result.url || result.build_warning) {
|
|
178
183
|
lines.push(" <properties>");
|
|
179
|
-
|
|
184
|
+
if (result.url) {
|
|
185
|
+
lines.push(` <property name="vibeview.run_url" value="${escapeXml(result.url)}" />`);
|
|
186
|
+
}
|
|
187
|
+
if (result.build_warning) {
|
|
188
|
+
lines.push(
|
|
189
|
+
` <property name="vibeview.build_warning" value="${escapeXml(result.build_warning)}" />`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
180
192
|
lines.push(" </properties>");
|
|
181
193
|
}
|
|
182
194
|
for (const tr of cases) {
|
|
@@ -225,46 +237,97 @@ function formatJunitError(name, message, url) {
|
|
|
225
237
|
return lines.join("\n");
|
|
226
238
|
}
|
|
227
239
|
var MULTI_TERMINAL = /* @__PURE__ */ new Set(["passed", "failed", "error", "cancelled"]);
|
|
240
|
+
function caseLines(c, classname) {
|
|
241
|
+
const name = escapeXml(c.name);
|
|
242
|
+
const head = ` <testcase name="${name}" classname="${classname}" time="${c.time}"`;
|
|
243
|
+
const out = c.url ? ` <system-out>${escapeXml(c.url)}</system-out>` : null;
|
|
244
|
+
if (c.verdict === "passed" && !out) return [`${head} />`];
|
|
245
|
+
const lines = [`${head}>`];
|
|
246
|
+
const msg = escapeXml(c.message ?? "");
|
|
247
|
+
if (c.verdict === "failure") {
|
|
248
|
+
lines.push(` <failure message="${msg}" type="TestFailure">${msg}</failure>`);
|
|
249
|
+
} else if (c.verdict === "error") {
|
|
250
|
+
lines.push(` <error message="${msg}" type="InfraError">${msg}</error>`);
|
|
251
|
+
} else if (c.verdict === "skipped") {
|
|
252
|
+
lines.push(` <skipped message="${msg}" />`);
|
|
253
|
+
}
|
|
254
|
+
if (out) lines.push(out);
|
|
255
|
+
lines.push(" </testcase>");
|
|
256
|
+
return lines;
|
|
257
|
+
}
|
|
228
258
|
function formatMultiSuiteJunit(result, opts = {}) {
|
|
229
|
-
const unfinished = (status) => opts.timedOut !== void 0 && !MULTI_TERMINAL.has(status);
|
|
230
|
-
const errored = (status) => status === "error" || unfinished(status);
|
|
231
|
-
const tests = result.suite_runs.reduce((sum, sr) => sum + sr.passed + sr.failed, 0);
|
|
232
|
-
const failures = result.suite_runs.reduce((sum, sr) => sum + sr.failed, 0);
|
|
233
|
-
const noSuites = opts.timedOut !== void 0 && result.suite_runs.length === 0;
|
|
234
|
-
const totalErrors = result.suite_runs.filter((sr) => errored(sr.status)).length + (noSuites ? 1 : 0);
|
|
259
|
+
const unfinished = (status) => opts.timedOut !== void 0 && !MULTI_TERMINAL.has(status ?? "");
|
|
235
260
|
const timestamp = result.started_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
261
|
+
const suites = result.suite_runs.map((sr) => {
|
|
262
|
+
const suiteName = sr.suite_name || sr.suite_id;
|
|
263
|
+
const cases = (sr.case_runs ?? []).map((c) => {
|
|
264
|
+
const name = c.case_name || c.run_id;
|
|
265
|
+
const time = c.duration_ms ? (c.duration_ms / 1e3).toFixed(1) : "0";
|
|
266
|
+
const base = { name, time, url: c.url || void 0 };
|
|
267
|
+
switch (c.status) {
|
|
268
|
+
case "passed":
|
|
269
|
+
return { ...base, verdict: "passed" };
|
|
270
|
+
case "failed":
|
|
271
|
+
return {
|
|
272
|
+
...base,
|
|
273
|
+
verdict: "failure",
|
|
274
|
+
message: c.error_message || "Test failed"
|
|
275
|
+
};
|
|
276
|
+
case "error":
|
|
277
|
+
return {
|
|
278
|
+
...base,
|
|
279
|
+
verdict: "error",
|
|
280
|
+
message: c.error_message || "Infrastructure error"
|
|
281
|
+
};
|
|
282
|
+
case "cancelled":
|
|
283
|
+
return { ...base, verdict: "skipped", message: c.error_message || "Cancelled" };
|
|
284
|
+
default:
|
|
285
|
+
return unfinished(c.status) ? {
|
|
286
|
+
...base,
|
|
287
|
+
verdict: "error",
|
|
288
|
+
message: `${opts.timedOut} (case was ${c.status ?? "not started"})`
|
|
289
|
+
} : { ...base, verdict: "skipped", message: "Not run" };
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
const suiteError = unfinished(sr.status) ? `${opts.timedOut} (suite was ${sr.status})` : sr.status === "error" ? sr.error_message || "Suite errored" : null;
|
|
293
|
+
if (suiteError && !cases.some((c) => c.verdict === "error")) {
|
|
294
|
+
cases.push({ name: suiteName, time: "0", verdict: "error", message: suiteError });
|
|
295
|
+
}
|
|
296
|
+
const legacy = sr.case_runs === void 0;
|
|
297
|
+
const tests2 = legacy ? sr.passed + sr.failed + (suiteError ? 1 : 0) : cases.length;
|
|
298
|
+
const failures = legacy ? sr.failed : cases.filter((c) => c.verdict === "failure").length;
|
|
299
|
+
const errors2 = cases.filter((c) => c.verdict === "error").length;
|
|
300
|
+
const skipped = cases.filter((c) => c.verdict === "skipped").length;
|
|
301
|
+
return { sr, suiteName, cases, tests: tests2, failures, errors: errors2, skipped };
|
|
302
|
+
});
|
|
303
|
+
const noSuites = opts.timedOut !== void 0 && result.suite_runs.length === 0;
|
|
304
|
+
const sum = (key) => suites.reduce((n, s) => n + s[key], 0);
|
|
305
|
+
const tests = sum("tests") + (noSuites ? 1 : 0);
|
|
306
|
+
const errors = sum("errors") + (noSuites ? 1 : 0);
|
|
236
307
|
const lines = [];
|
|
237
308
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
238
309
|
lines.push(
|
|
239
|
-
`<testsuites name="vibeview-multi" tests="${tests}" failures="${failures}" errors="${
|
|
310
|
+
`<testsuites name="vibeview-multi" tests="${tests}" failures="${sum("failures")}" errors="${errors}" skipped="${sum("skipped")}">`
|
|
240
311
|
);
|
|
241
|
-
for (const
|
|
242
|
-
const suiteName = escapeXml(
|
|
243
|
-
const suiteTests = sr.passed + sr.failed;
|
|
244
|
-
const suiteFails = sr.failed;
|
|
245
|
-
const suiteErrors = errored(sr.status) ? 1 : 0;
|
|
312
|
+
for (const s of suites) {
|
|
313
|
+
const suiteName = escapeXml(s.suiteName);
|
|
246
314
|
lines.push(
|
|
247
|
-
` <testsuite name="${suiteName}" tests="${
|
|
315
|
+
` <testsuite name="${suiteName}" tests="${s.tests}" failures="${s.failures}" errors="${s.errors}" skipped="${s.skipped}" timestamp="${timestamp}">`
|
|
248
316
|
);
|
|
249
|
-
if (sr.url) {
|
|
317
|
+
if (s.sr.url) {
|
|
250
318
|
lines.push(" <properties>");
|
|
251
|
-
lines.push(` <property name="vibeview.run_url" value="${escapeXml(sr.url)}" />`);
|
|
319
|
+
lines.push(` <property name="vibeview.run_url" value="${escapeXml(s.sr.url)}" />`);
|
|
252
320
|
lines.push(" </properties>");
|
|
253
321
|
}
|
|
254
|
-
|
|
255
|
-
const msg = escapeXml(`${opts.timedOut} (suite was ${sr.status})`);
|
|
256
|
-
lines.push(` <error message="${msg}" type="InfraError" />`);
|
|
257
|
-
} else if (suiteErrors > 0) {
|
|
258
|
-
lines.push(` <error message="Suite errored" type="InfraError" />`);
|
|
259
|
-
}
|
|
322
|
+
for (const c of s.cases) lines.push(...caseLines(c, suiteName));
|
|
260
323
|
lines.push(" </testsuite>");
|
|
261
324
|
}
|
|
262
325
|
if (noSuites) {
|
|
263
|
-
const
|
|
326
|
+
const name = "vibeview run-suites";
|
|
264
327
|
lines.push(
|
|
265
|
-
` <testsuite name="
|
|
328
|
+
` <testsuite name="${name}" tests="1" failures="0" errors="1" skipped="0" timestamp="${timestamp}">`
|
|
266
329
|
);
|
|
267
|
-
lines.push(
|
|
330
|
+
lines.push(...caseLines({ name, time: "0", verdict: "error", message: opts.timedOut }, name));
|
|
268
331
|
lines.push(" </testsuite>");
|
|
269
332
|
}
|
|
270
333
|
lines.push("</testsuites>");
|
|
@@ -481,15 +544,20 @@ function formatAction(action) {
|
|
|
481
544
|
function formatToolCall(tc) {
|
|
482
545
|
return formatAction({ tool: tc.name, args: tc.args });
|
|
483
546
|
}
|
|
547
|
+
function runStartedLine(evt) {
|
|
548
|
+
const steps = `${evt.total_steps} step(s)`;
|
|
549
|
+
const model = chalk2.bold(evt.model || "default");
|
|
550
|
+
if (evt.mode === "replay") return `${chalk2.cyan("\u25B6")} Replay started \u2014 ${steps} (no AI)`;
|
|
551
|
+
if (evt.mode === "hybrid") {
|
|
552
|
+
return `${chalk2.cyan("\u25B6")} Hybrid test started \u2014 ${steps}, replaying the recording, AI fallback model: ${model}`;
|
|
553
|
+
}
|
|
554
|
+
return `${chalk2.cyan("\u25B6")} AI test started \u2014 ${steps}, model: ${model}`;
|
|
555
|
+
}
|
|
484
556
|
function displayProgressEvent(evt, ctx) {
|
|
485
557
|
const { spinner } = ctx;
|
|
486
558
|
switch (evt.event) {
|
|
487
559
|
case "run_started":
|
|
488
|
-
printBelowSpinner(
|
|
489
|
-
spinner,
|
|
490
|
-
`${chalk2.cyan("\u25B6")} AI test started \u2014 ${evt.total_steps} step(s), model: ${chalk2.bold(evt.model || "default")}`,
|
|
491
|
-
" Starting test..."
|
|
492
|
-
);
|
|
560
|
+
printBelowSpinner(spinner, runStartedLine(evt), " Starting test...");
|
|
493
561
|
ctx.totalSteps = evt.total_steps ?? ctx.totalSteps;
|
|
494
562
|
break;
|
|
495
563
|
case "step_started": {
|
|
@@ -572,6 +640,7 @@ function mapExitCode(status) {
|
|
|
572
640
|
}
|
|
573
641
|
|
|
574
642
|
// src/lib/run-target.ts
|
|
643
|
+
import chalk3 from "chalk";
|
|
575
644
|
var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
576
645
|
function parseRunPlatform(value) {
|
|
577
646
|
if (value === void 0) return void 0;
|
|
@@ -580,6 +649,18 @@ function parseRunPlatform(value) {
|
|
|
580
649
|
}
|
|
581
650
|
return value;
|
|
582
651
|
}
|
|
652
|
+
function refusePlatformWithApp(options) {
|
|
653
|
+
if (options.platform && options.app) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
"--platform and --app cannot be combined: --app already names the app, and so the platform, the run uses. Pass --app <id> to pick an app, or --platform to run the app with the same bundle id / package on that platform."
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function printBuildWarning(warning, label) {
|
|
660
|
+
if (!warning) return;
|
|
661
|
+
const prefix = label ? `warning (${label}):` : "warning:";
|
|
662
|
+
process.stderr.write(chalk3.yellow(`${prefix} ${warning}`) + "\n");
|
|
663
|
+
}
|
|
583
664
|
function describeRunTarget(response, options) {
|
|
584
665
|
const asked = Boolean(options.platform || options.app);
|
|
585
666
|
if (!response.app_id) {
|
|
@@ -668,12 +749,13 @@ ${signal} received.
|
|
|
668
749
|
const target = describeRunTarget(response, options);
|
|
669
750
|
spinner.stop();
|
|
670
751
|
process.stderr.write(
|
|
671
|
-
`${
|
|
672
|
-
` + (target ? `${
|
|
673
|
-
` : "") + `${
|
|
674
|
-
` + (sessionId ? `${
|
|
752
|
+
`${chalk4.dim("Suite:")} ${suiteId}
|
|
753
|
+
` + (target ? `${chalk4.dim("App:")} ${target}
|
|
754
|
+
` : "") + `${chalk4.dim("Run:")} ${response.run_id}
|
|
755
|
+
` + (sessionId ? `${chalk4.dim("Session:")} ${sessionId}
|
|
675
756
|
` : "")
|
|
676
757
|
);
|
|
758
|
+
printBuildWarning(response.build_warning);
|
|
677
759
|
spinner.start("Waiting for device...");
|
|
678
760
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
679
761
|
const result = await pollUntilComplete(
|
|
@@ -691,7 +773,10 @@ ${signal} received.
|
|
|
691
773
|
process.stdin.setRawMode(false);
|
|
692
774
|
process.stdin.pause();
|
|
693
775
|
}
|
|
694
|
-
outputResult(
|
|
776
|
+
outputResult(
|
|
777
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
778
|
+
options
|
|
779
|
+
);
|
|
695
780
|
process.exit(mapExitCode(result.status));
|
|
696
781
|
} catch (err) {
|
|
697
782
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -717,7 +802,7 @@ ${signal} received.
|
|
|
717
802
|
}
|
|
718
803
|
|
|
719
804
|
// src/commands/run-suites.ts
|
|
720
|
-
import
|
|
805
|
+
import chalk5 from "chalk";
|
|
721
806
|
var AGGREGATE_TIMEOUT_CAP_S = 7200;
|
|
722
807
|
async function runSuites(opts) {
|
|
723
808
|
if (!opts.tag || opts.tag.length === 0) {
|
|
@@ -792,13 +877,13 @@ ${signal} received.
|
|
|
792
877
|
const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
|
|
793
878
|
if (opts.output === "json") printJsonError(noMatch);
|
|
794
879
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", noMatch));
|
|
795
|
-
process.stderr.write(
|
|
880
|
+
process.stderr.write(chalk5.yellow(`${noMatch}
|
|
796
881
|
`));
|
|
797
882
|
process.exit(EXIT_INFRA_ERROR);
|
|
798
883
|
}
|
|
799
884
|
if (opts.output === "json") printJsonError(err);
|
|
800
885
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", msg));
|
|
801
|
-
process.stderr.write(
|
|
886
|
+
process.stderr.write(chalk5.red(`Failed to start: ${msg}
|
|
802
887
|
`));
|
|
803
888
|
process.exit(EXIT_INFRA_ERROR);
|
|
804
889
|
}
|
|
@@ -807,16 +892,17 @@ ${signal} received.
|
|
|
807
892
|
const matchedNames = start.matched_suites?.map((s) => s.name) ?? [];
|
|
808
893
|
const totalMatched = start.matched_suite_ids.length;
|
|
809
894
|
process.stderr.write(
|
|
810
|
-
`${
|
|
811
|
-
${
|
|
812
|
-
${
|
|
895
|
+
`${chalk5.dim("Aggregate:")} ${start.aggregate_id}
|
|
896
|
+
${chalk5.dim("Tags:")} ${opts.tag.join(", ")} ${chalk5.dim("(" + (opts.match ?? "any") + ")")}
|
|
897
|
+
${chalk5.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u2014 " + matchedNames.join(", ") : ""}
|
|
813
898
|
`
|
|
814
899
|
);
|
|
900
|
+
for (const s of start.matched_suites ?? []) printBuildWarning(s.build_warning, s.name);
|
|
815
901
|
const excluded = start.excluded_suites ?? [];
|
|
816
902
|
if (excluded.length) {
|
|
817
903
|
const names = excluded.map((s) => s.name).join(", ");
|
|
818
904
|
process.stderr.write(
|
|
819
|
-
|
|
905
|
+
chalk5.dim(`Excluded ${excluded.length} suite(s) (platform mismatch): ${names}
|
|
820
906
|
`)
|
|
821
907
|
);
|
|
822
908
|
}
|
|
@@ -830,20 +916,28 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
830
916
|
const suiteStateBefore = /* @__PURE__ */ new Map();
|
|
831
917
|
const suiteStartedPrinted = /* @__PURE__ */ new Set();
|
|
832
918
|
let lastQueueReason = null;
|
|
919
|
+
let pending = [];
|
|
833
920
|
const print = (line) => {
|
|
921
|
+
pending.push(line);
|
|
922
|
+
};
|
|
923
|
+
const flush = (nextText) => {
|
|
924
|
+
const textChanged = nextText !== null && nextText !== spinner.text;
|
|
925
|
+
if (pending.length === 0 && !textChanged) return;
|
|
834
926
|
spinner.stop();
|
|
835
|
-
process.stderr.write(line + "\n");
|
|
836
|
-
|
|
927
|
+
for (const line of pending) process.stderr.write(line + "\n");
|
|
928
|
+
pending = [];
|
|
929
|
+
if (nextText !== null) spinner.start(nextText);
|
|
837
930
|
};
|
|
838
931
|
const TERMINAL = /* @__PURE__ */ new Set(["passed", "failed", "error", "cancelled"]);
|
|
839
932
|
let last = null;
|
|
840
933
|
const abandon = async (reason) => {
|
|
934
|
+
flush(null);
|
|
841
935
|
spinner.stop();
|
|
842
936
|
if (process.stdin.isTTY) {
|
|
843
937
|
process.stdin.setRawMode(false);
|
|
844
938
|
process.stdin.pause();
|
|
845
939
|
}
|
|
846
|
-
process.stderr.write(
|
|
940
|
+
process.stderr.write(chalk5.red(`${reason}
|
|
847
941
|
`));
|
|
848
942
|
process.stderr.write("Cancelling multi-suite run...\n");
|
|
849
943
|
try {
|
|
@@ -851,7 +945,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
851
945
|
process.stderr.write("Cancellation requested.\n");
|
|
852
946
|
} catch (err) {
|
|
853
947
|
const m = err instanceof Error ? err.message : String(err);
|
|
854
|
-
process.stderr.write(
|
|
948
|
+
process.stderr.write(chalk5.yellow(`Could not cancel the run: ${m}
|
|
855
949
|
`));
|
|
856
950
|
}
|
|
857
951
|
if (opts.output === "json") {
|
|
@@ -879,6 +973,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
879
973
|
return abandon(`Could not read the run's status: ${m}`);
|
|
880
974
|
}
|
|
881
975
|
let doneCount = 0;
|
|
976
|
+
let nextText;
|
|
882
977
|
let queuedChild;
|
|
883
978
|
for (const sr of last.suite_runs) {
|
|
884
979
|
const cur = sr.status;
|
|
@@ -888,17 +983,17 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
888
983
|
}
|
|
889
984
|
if (cur === "running" && !suiteStartedPrinted.has(sr.run_id)) {
|
|
890
985
|
suiteStartedPrinted.add(sr.run_id);
|
|
891
|
-
print(`${
|
|
986
|
+
print(`${chalk5.cyan("\u25B6")} ${chalk5.bold(sr.suite_name)} started`);
|
|
892
987
|
}
|
|
893
988
|
const wasTerminal = prev !== void 0 && TERMINAL.has(prev);
|
|
894
989
|
if (TERMINAL.has(cur) && !wasTerminal) {
|
|
895
|
-
const mark = cur === "passed" ?
|
|
896
|
-
const link = sr.url ?
|
|
990
|
+
const mark = cur === "passed" ? chalk5.green("\u2713") : cur === "cancelled" ? chalk5.yellow("\u26A0") : chalk5.red("\u2717");
|
|
991
|
+
const link = sr.url ? chalk5.dim(" " + sr.url) : "";
|
|
897
992
|
print(
|
|
898
|
-
`${mark} ${
|
|
993
|
+
`${mark} ${chalk5.bold(sr.suite_name)} ${cur}: ${sr.passed} passed, ${sr.failed} failed${link}`
|
|
899
994
|
);
|
|
900
995
|
if (sr.error_message) {
|
|
901
|
-
print(` ${
|
|
996
|
+
print(` ${chalk5.red(sr.error_message)}`);
|
|
902
997
|
}
|
|
903
998
|
}
|
|
904
999
|
suiteStateBefore.set(sr.run_id, cur);
|
|
@@ -911,16 +1006,20 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
911
1006
|
const why = queueReasonCopy(q.reason);
|
|
912
1007
|
if (why && why !== lastQueueReason) {
|
|
913
1008
|
print(
|
|
914
|
-
`${
|
|
1009
|
+
`${chalk5.yellow("\u23F3")} ${chalk5.bold(queuedChild.suite_name)} is waiting for a device: ${why}`
|
|
915
1010
|
);
|
|
916
1011
|
}
|
|
917
1012
|
lastQueueReason = why;
|
|
918
|
-
|
|
1013
|
+
nextText = `Queued \u2014 ${chalk5.bold(queuedChild.suite_name)} position ${q.position} for ${target} (${doneCount}/${totalMatched} suite(s) complete)`;
|
|
919
1014
|
} else {
|
|
920
1015
|
lastQueueReason = null;
|
|
921
|
-
|
|
1016
|
+
nextText = `Running suites \u2014 ${doneCount}/${totalMatched} complete...`;
|
|
922
1017
|
}
|
|
923
|
-
if (TERMINAL.has(last.status))
|
|
1018
|
+
if (TERMINAL.has(last.status)) {
|
|
1019
|
+
flush(null);
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
flush(nextText);
|
|
924
1023
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
925
1024
|
}
|
|
926
1025
|
if (!last || !TERMINAL.has(last.status)) {
|
|
@@ -934,7 +1033,9 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
934
1033
|
process.stdin.pause();
|
|
935
1034
|
}
|
|
936
1035
|
if (opts.output === "json") {
|
|
937
|
-
|
|
1036
|
+
const buildWarnings = (start.matched_suites ?? []).filter((s) => s.build_warning).map((s) => ({ suite_id: s.id, suite_name: s.name, build_warning: s.build_warning }));
|
|
1037
|
+
const doc = buildWarnings.length ? { ...last, build_warnings: buildWarnings } : last;
|
|
1038
|
+
process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
|
|
938
1039
|
} else if (opts.output === "junit") {
|
|
939
1040
|
process.stdout.write(formatMultiSuiteJunit(last) + "\n");
|
|
940
1041
|
} else {
|
|
@@ -942,25 +1043,25 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
942
1043
|
Aggregate: ${last.status}
|
|
943
1044
|
`);
|
|
944
1045
|
for (const sr of last.suite_runs) {
|
|
945
|
-
const mark = sr.status === "passed" ?
|
|
946
|
-
const link = sr.url ?
|
|
1046
|
+
const mark = sr.status === "passed" ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
1047
|
+
const link = sr.url ? chalk5.dim(` ${sr.url}`) : "";
|
|
947
1048
|
process.stdout.write(
|
|
948
1049
|
` ${mark} ${sr.suite_name}: ${sr.passed} passed, ${sr.failed} failed${link}
|
|
949
1050
|
`
|
|
950
1051
|
);
|
|
951
1052
|
if (sr.error_message) {
|
|
952
|
-
process.stdout.write(` ${
|
|
1053
|
+
process.stdout.write(` ${chalk5.red(sr.error_message)}
|
|
953
1054
|
`);
|
|
954
1055
|
}
|
|
955
1056
|
for (const cr of sr.case_runs ?? []) {
|
|
956
|
-
const caseMark = cr.status === "passed" ?
|
|
957
|
-
const dur = cr.duration_ms ?
|
|
1057
|
+
const caseMark = cr.status === "passed" ? chalk5.green("\u2713") : cr.status === "cancelled" || cr.status === "pending" || cr.status === "running" ? chalk5.yellow("-") : chalk5.red("\u2717");
|
|
1058
|
+
const dur = cr.duration_ms ? chalk5.dim(` (${(cr.duration_ms / 1e3).toFixed(1)}s)`) : "";
|
|
958
1059
|
process.stdout.write(
|
|
959
|
-
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${
|
|
1060
|
+
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${chalk5.dim(cr.url)}
|
|
960
1061
|
`
|
|
961
1062
|
);
|
|
962
1063
|
if (cr.error_message && cr.status !== "passed") {
|
|
963
|
-
process.stdout.write(` ${
|
|
1064
|
+
process.stdout.write(` ${chalk5.red(cr.error_message)}
|
|
964
1065
|
`);
|
|
965
1066
|
}
|
|
966
1067
|
}
|
|
@@ -972,7 +1073,7 @@ Aggregate: ${last.status}
|
|
|
972
1073
|
}
|
|
973
1074
|
|
|
974
1075
|
// src/commands/run-test.ts
|
|
975
|
-
import
|
|
1076
|
+
import chalk6 from "chalk";
|
|
976
1077
|
async function runTest(testId, options) {
|
|
977
1078
|
const { token, baseUrl } = resolveAuth(options);
|
|
978
1079
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1046,13 +1147,14 @@ ${signal} received.
|
|
|
1046
1147
|
const target = describeRunTarget(response, options);
|
|
1047
1148
|
spinner.stop();
|
|
1048
1149
|
process.stderr.write(
|
|
1049
|
-
`${
|
|
1050
|
-
${
|
|
1051
|
-
${
|
|
1052
|
-
` + (target ? `${
|
|
1053
|
-
` : "") + `${
|
|
1150
|
+
`${chalk6.dim("Session:")} ${sessionId || "unknown"}
|
|
1151
|
+
${chalk6.dim("Device:")} ${response.device_name || "allocating..."}
|
|
1152
|
+
${chalk6.dim("Test:")} ${testId}
|
|
1153
|
+
` + (target ? `${chalk6.dim("App:")} ${target}
|
|
1154
|
+
` : "") + `${chalk6.dim("Run:")} ${response.run_id}
|
|
1054
1155
|
`
|
|
1055
1156
|
);
|
|
1157
|
+
printBuildWarning(response.build_warning);
|
|
1056
1158
|
spinner.start("Waiting for device...");
|
|
1057
1159
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
1058
1160
|
const result = await pollUntilComplete(
|
|
@@ -1070,7 +1172,10 @@ ${chalk5.dim("Test:")} ${testId}
|
|
|
1070
1172
|
process.stdin.setRawMode(false);
|
|
1071
1173
|
process.stdin.pause();
|
|
1072
1174
|
}
|
|
1073
|
-
outputResult(
|
|
1175
|
+
outputResult(
|
|
1176
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
1177
|
+
options
|
|
1178
|
+
);
|
|
1074
1179
|
process.exit(mapExitCode(result.status));
|
|
1075
1180
|
} catch (err) {
|
|
1076
1181
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -1096,7 +1201,7 @@ ${chalk5.dim("Test:")} ${testId}
|
|
|
1096
1201
|
}
|
|
1097
1202
|
|
|
1098
1203
|
// src/commands/list-suites.ts
|
|
1099
|
-
import
|
|
1204
|
+
import chalk7 from "chalk";
|
|
1100
1205
|
async function listSuites(options) {
|
|
1101
1206
|
const { token, baseUrl } = resolveAuth(options);
|
|
1102
1207
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1118,21 +1223,21 @@ async function listSuites(options) {
|
|
|
1118
1223
|
console.log("No test suites found.");
|
|
1119
1224
|
return;
|
|
1120
1225
|
}
|
|
1121
|
-
console.log(
|
|
1226
|
+
console.log(chalk7.bold(`
|
|
1122
1227
|
${suites.length} suite(s)
|
|
1123
1228
|
`));
|
|
1124
1229
|
for (const s of suites) {
|
|
1125
|
-
const platform = s.platform ?
|
|
1126
|
-
const tagsText = s.tags && s.tags.length > 0 ?
|
|
1230
|
+
const platform = s.platform ? chalk7.dim(` [${s.platform}]`) : "";
|
|
1231
|
+
const tagsText = s.tags && s.tags.length > 0 ? chalk7.dim(` [${s.tags.map((t) => t.display_name).join(", ")}]`) : "";
|
|
1127
1232
|
console.log(
|
|
1128
|
-
` ${
|
|
1233
|
+
` ${chalk7.cyan(s.id)} ${s.name}${platform}${tagsText} (${s.case_count} ${s.case_count === 1 ? "case" : "cases"})`
|
|
1129
1234
|
);
|
|
1130
1235
|
}
|
|
1131
1236
|
console.log();
|
|
1132
1237
|
}
|
|
1133
1238
|
|
|
1134
1239
|
// src/commands/list-apps.ts
|
|
1135
|
-
import
|
|
1240
|
+
import chalk8 from "chalk";
|
|
1136
1241
|
async function listApps(options) {
|
|
1137
1242
|
const { token, baseUrl } = resolveAuth(options);
|
|
1138
1243
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1151,20 +1256,20 @@ async function listApps(options) {
|
|
|
1151
1256
|
console.log("No apps found.");
|
|
1152
1257
|
return;
|
|
1153
1258
|
}
|
|
1154
|
-
console.log(
|
|
1259
|
+
console.log(chalk8.bold(`
|
|
1155
1260
|
${apps.length} app(s)
|
|
1156
1261
|
`));
|
|
1157
1262
|
for (const app of apps) {
|
|
1158
|
-
const platform =
|
|
1159
|
-
const pkg2 =
|
|
1160
|
-
console.log(` ${
|
|
1161
|
-
console.log(
|
|
1263
|
+
const platform = chalk8.dim(` [${app.platform}]`);
|
|
1264
|
+
const pkg2 = chalk8.dim(app.package_name);
|
|
1265
|
+
console.log(` ${chalk8.cyan(app.public_id)} ${app.name}${platform} ${pkg2}`);
|
|
1266
|
+
console.log(chalk8.dim(` ${baseUrl}/apps/${app.public_id}`));
|
|
1162
1267
|
}
|
|
1163
1268
|
console.log();
|
|
1164
1269
|
}
|
|
1165
1270
|
|
|
1166
1271
|
// src/commands/stop.ts
|
|
1167
|
-
import
|
|
1272
|
+
import chalk9 from "chalk";
|
|
1168
1273
|
async function stopCommand(sessionId, opts, deps) {
|
|
1169
1274
|
const id = sessionId?.trim();
|
|
1170
1275
|
if (!id) throw new UsageError("a session id is required: vibeview stop <session-id>");
|
|
@@ -1175,7 +1280,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1175
1280
|
} catch (err) {
|
|
1176
1281
|
const message = err instanceof Error ? err.message : String(err);
|
|
1177
1282
|
if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
|
|
1178
|
-
console.error(
|
|
1283
|
+
console.error(chalk9.red(`error: ${message}`));
|
|
1179
1284
|
process.exitCode = errorExitCode(err);
|
|
1180
1285
|
return;
|
|
1181
1286
|
}
|
|
@@ -1208,7 +1313,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1208
1313
|
}
|
|
1209
1314
|
|
|
1210
1315
|
// src/commands/create-test.ts
|
|
1211
|
-
import
|
|
1316
|
+
import chalk10 from "chalk";
|
|
1212
1317
|
import { existsSync, readFileSync } from "fs";
|
|
1213
1318
|
async function readStdin() {
|
|
1214
1319
|
return new Promise((resolve, reject) => {
|
|
@@ -1249,6 +1354,21 @@ async function resolveSteps(options) {
|
|
|
1249
1354
|
}
|
|
1250
1355
|
throw new Error("No steps provided. Use --steps, --steps-file, or pipe JSON to stdin.");
|
|
1251
1356
|
}
|
|
1357
|
+
function testCaseUrl(baseUrl, publicId) {
|
|
1358
|
+
return `${baseUrl}/tests/cases/${encodeURIComponent(publicId)}`;
|
|
1359
|
+
}
|
|
1360
|
+
function assertHasSteps(steps) {
|
|
1361
|
+
if (!Array.isArray(steps)) {
|
|
1362
|
+
throw new Error(
|
|
1363
|
+
`Steps must be a JSON array of instructions, e.g. '["Open the app", "Verify the Home screen shows"]'.`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (steps.length === 0) {
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
`A test needs at least one step. Pass a JSON array with one or more instructions, e.g. --steps '["Verify the Home screen shows a list of trips"]'.`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1252
1372
|
async function resolveAppId(client, appRef, platform) {
|
|
1253
1373
|
if (appRef.includes(".")) {
|
|
1254
1374
|
if (!platform) {
|
|
@@ -1270,6 +1390,7 @@ async function createTest(options) {
|
|
|
1270
1390
|
const client = new ApiClient(baseUrl, token);
|
|
1271
1391
|
try {
|
|
1272
1392
|
const steps = await resolveSteps(options);
|
|
1393
|
+
assertHasSteps(steps);
|
|
1273
1394
|
let appPublicId;
|
|
1274
1395
|
if (options.app) {
|
|
1275
1396
|
appPublicId = await resolveAppId(client, options.app, options.platform);
|
|
@@ -1295,7 +1416,7 @@ async function createTest(options) {
|
|
|
1295
1416
|
name: result.name,
|
|
1296
1417
|
app_id: appPublicId || null,
|
|
1297
1418
|
outcome: result.outcome ?? null,
|
|
1298
|
-
url:
|
|
1419
|
+
url: testCaseUrl(baseUrl, result.public_id)
|
|
1299
1420
|
},
|
|
1300
1421
|
null,
|
|
1301
1422
|
2
|
|
@@ -1303,20 +1424,20 @@ async function createTest(options) {
|
|
|
1303
1424
|
);
|
|
1304
1425
|
} else {
|
|
1305
1426
|
console.log(`
|
|
1306
|
-
${
|
|
1427
|
+
${chalk10.cyan(result.public_id)} ${result.name}`);
|
|
1307
1428
|
if (result.app_name) {
|
|
1308
|
-
console.log(` ${
|
|
1429
|
+
console.log(` ${chalk10.dim(`App: ${result.app_name}`)}`);
|
|
1309
1430
|
}
|
|
1310
1431
|
if (result.platform) {
|
|
1311
|
-
console.log(` ${
|
|
1432
|
+
console.log(` ${chalk10.dim(`Platform: ${result.platform}`)}`);
|
|
1312
1433
|
}
|
|
1313
|
-
console.log(` ${
|
|
1314
|
-
console.log(
|
|
1434
|
+
console.log(` ${chalk10.dim(`Steps: ${steps.length}`)}`);
|
|
1435
|
+
console.log(chalk10.dim(` ${testCaseUrl(baseUrl, result.public_id)}
|
|
1315
1436
|
`));
|
|
1316
1437
|
}
|
|
1317
1438
|
} catch (err) {
|
|
1318
1439
|
if (options.output === "json") printJsonError(err);
|
|
1319
|
-
console.error(
|
|
1440
|
+
console.error(chalk10.red(err instanceof Error ? err.message : "Failed to create test"));
|
|
1320
1441
|
process.exit(
|
|
1321
1442
|
err instanceof ApiStatusError && err.status === 409 ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR
|
|
1322
1443
|
);
|
|
@@ -1400,15 +1521,15 @@ async function logout() {
|
|
|
1400
1521
|
}
|
|
1401
1522
|
|
|
1402
1523
|
// src/commands/dev-status.ts
|
|
1403
|
-
import
|
|
1524
|
+
import chalk11 from "chalk";
|
|
1404
1525
|
function buildLine(info) {
|
|
1405
1526
|
if (!info || typeof info.app_build_id !== "number") return null;
|
|
1406
1527
|
const version = info.app_version ? ` version ${info.app_version}${info.app_build_number ? ` (${info.app_build_number})` : ""}` : "";
|
|
1407
1528
|
return `${info.app_build_id}${version}`;
|
|
1408
1529
|
}
|
|
1409
1530
|
async function devStatus(opts) {
|
|
1410
|
-
const
|
|
1411
|
-
if (!
|
|
1531
|
+
const found = readDevStateFile(process.cwd());
|
|
1532
|
+
if (!found) {
|
|
1412
1533
|
if (opts.json) {
|
|
1413
1534
|
console.log(JSON.stringify({ running: false }));
|
|
1414
1535
|
} else {
|
|
@@ -1419,6 +1540,11 @@ async function devStatus(opts) {
|
|
|
1419
1540
|
}
|
|
1420
1541
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1421
1542
|
const api = new ApiClient(baseUrl, token);
|
|
1543
|
+
if (!found.alive) {
|
|
1544
|
+
await reportOrphan(api, found.state, opts);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const state = found.state;
|
|
1422
1548
|
let sessionStatus;
|
|
1423
1549
|
let info;
|
|
1424
1550
|
try {
|
|
@@ -1456,14 +1582,49 @@ async function devStatus(opts) {
|
|
|
1456
1582
|
console.error(` url: ${state.page_url}`);
|
|
1457
1583
|
console.error(` started: ${state.started_at}`);
|
|
1458
1584
|
}
|
|
1459
|
-
if (metro) console.error(
|
|
1585
|
+
if (metro) console.error(chalk11.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
|
|
1460
1586
|
process.exit(0);
|
|
1461
1587
|
}
|
|
1588
|
+
var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "queued", "starting", "active"]);
|
|
1589
|
+
async function reportOrphan(api, state, opts) {
|
|
1590
|
+
let status;
|
|
1591
|
+
try {
|
|
1592
|
+
status = (await api.getSession(state.session_id)).status;
|
|
1593
|
+
} catch {
|
|
1594
|
+
status = void 0;
|
|
1595
|
+
}
|
|
1596
|
+
const live = status !== void 0 && LIVE_STATUSES.has(status);
|
|
1597
|
+
if (!live && status !== void 0) clearDevState(process.cwd());
|
|
1598
|
+
if (opts.json) {
|
|
1599
|
+
console.log(
|
|
1600
|
+
JSON.stringify({
|
|
1601
|
+
running: false,
|
|
1602
|
+
pid: state.pid,
|
|
1603
|
+
session_id: state.session_id,
|
|
1604
|
+
session_status: status ?? null,
|
|
1605
|
+
orphaned: live
|
|
1606
|
+
})
|
|
1607
|
+
);
|
|
1608
|
+
} else if (live) {
|
|
1609
|
+
console.error(
|
|
1610
|
+
chalk11.yellow(
|
|
1611
|
+
`\u26A0 The dev process (pid ${state.pid}) has exited, but session ${state.session_id} is still ${status}.`
|
|
1612
|
+
)
|
|
1613
|
+
);
|
|
1614
|
+
console.error(` End it: vibeview stop ${state.session_id}`);
|
|
1615
|
+
} else {
|
|
1616
|
+
console.error(
|
|
1617
|
+
`No dev session running \u2014 the dev process (pid ${state.pid}) has exited` + (status ? ` and session ${state.session_id} is ${status}.` : ".")
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1462
1622
|
|
|
1463
1623
|
// src/commands/dev-stop.ts
|
|
1464
1624
|
async function devStop(opts) {
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1625
|
+
const found = readDevStateFile(process.cwd());
|
|
1626
|
+
const state = found?.state;
|
|
1627
|
+
if (!found || !state) {
|
|
1467
1628
|
if (opts.json) {
|
|
1468
1629
|
console.log(JSON.stringify({ stopped: false, reason: "no_dev_session" }));
|
|
1469
1630
|
} else {
|
|
@@ -1476,10 +1637,12 @@ async function devStop(opts) {
|
|
|
1476
1637
|
const api = new ApiClient(baseUrl, token);
|
|
1477
1638
|
await api.stopSession(state.session_id).catch(() => {
|
|
1478
1639
|
});
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1640
|
+
if (found.alive) {
|
|
1641
|
+
try {
|
|
1642
|
+
process.kill(state.pid, "SIGTERM");
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
if (err.code !== "ESRCH") throw err;
|
|
1645
|
+
}
|
|
1483
1646
|
}
|
|
1484
1647
|
clearDevState(process.cwd());
|
|
1485
1648
|
killOwnedMetro(process.cwd());
|
|
@@ -1492,12 +1655,12 @@ async function devStop(opts) {
|
|
|
1492
1655
|
}
|
|
1493
1656
|
|
|
1494
1657
|
// src/commands/dev-reload.ts
|
|
1495
|
-
import
|
|
1658
|
+
import chalk12 from "chalk";
|
|
1496
1659
|
function fail(opts, code, message, exit) {
|
|
1497
1660
|
if (opts.json) {
|
|
1498
1661
|
console.log(JSON.stringify({ reloaded: false, code, message }));
|
|
1499
1662
|
} else {
|
|
1500
|
-
console.error(
|
|
1663
|
+
console.error(chalk12.red(`error: ${message}`));
|
|
1501
1664
|
}
|
|
1502
1665
|
process.exit(exit);
|
|
1503
1666
|
}
|
|
@@ -1550,7 +1713,7 @@ async function devReload(opts) {
|
|
|
1550
1713
|
api,
|
|
1551
1714
|
// Build output and the "reloaded in" line are human output: stderr, so
|
|
1552
1715
|
// stdout stays reserved for the --json result.
|
|
1553
|
-
log: (line) => console.error(
|
|
1716
|
+
log: (line) => console.error(chalk12.dim(line))
|
|
1554
1717
|
});
|
|
1555
1718
|
const started = Date.now();
|
|
1556
1719
|
try {
|
|
@@ -1571,10 +1734,10 @@ async function devReload(opts) {
|
|
|
1571
1734
|
}
|
|
1572
1735
|
|
|
1573
1736
|
// src/commands/builds.ts
|
|
1574
|
-
import
|
|
1737
|
+
import chalk14 from "chalk";
|
|
1575
1738
|
|
|
1576
1739
|
// src/lib/formatters/builds.ts
|
|
1577
|
-
import
|
|
1740
|
+
import chalk13 from "chalk";
|
|
1578
1741
|
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1579
1742
|
var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
1580
1743
|
function formatCostCell(costMicro, status) {
|
|
@@ -1582,13 +1745,13 @@ function formatCostCell(costMicro, status) {
|
|
|
1582
1745
|
return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
|
|
1583
1746
|
}
|
|
1584
1747
|
var STATUS_COLOR = {
|
|
1585
|
-
succeeded:
|
|
1586
|
-
failed:
|
|
1587
|
-
timed_out:
|
|
1588
|
-
infra_failed:
|
|
1589
|
-
canceled:
|
|
1590
|
-
running:
|
|
1591
|
-
queued:
|
|
1748
|
+
succeeded: chalk13.green,
|
|
1749
|
+
failed: chalk13.red,
|
|
1750
|
+
timed_out: chalk13.red,
|
|
1751
|
+
infra_failed: chalk13.red,
|
|
1752
|
+
canceled: chalk13.dim,
|
|
1753
|
+
running: chalk13.yellow,
|
|
1754
|
+
queued: chalk13.dim
|
|
1592
1755
|
};
|
|
1593
1756
|
function formatBuildsHuman(builds2) {
|
|
1594
1757
|
if (builds2.length === 0) {
|
|
@@ -1605,7 +1768,7 @@ function formatBuildsHuman(builds2) {
|
|
|
1605
1768
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1606
1769
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1607
1770
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
1608
|
-
const lines = [
|
|
1771
|
+
const lines = [chalk13.bold(join4(padCells(HEADERS)))];
|
|
1609
1772
|
for (const row of rows) {
|
|
1610
1773
|
const padded = padCells(row);
|
|
1611
1774
|
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
@@ -1664,7 +1827,7 @@ async function buildsLogs(id, opts, deps) {
|
|
|
1664
1827
|
console.log(JSON.stringify(logs));
|
|
1665
1828
|
} else {
|
|
1666
1829
|
if (logs.content) process.stdout.write(logs.content);
|
|
1667
|
-
console.error(
|
|
1830
|
+
console.error(chalk14.dim(`status: ${logs.status}`));
|
|
1668
1831
|
}
|
|
1669
1832
|
process.exitCode = 0;
|
|
1670
1833
|
return;
|
|
@@ -1761,7 +1924,7 @@ async function buildsDownload(id, opts, deps) {
|
|
|
1761
1924
|
if (opts.json) {
|
|
1762
1925
|
console.log(JSON.stringify(result));
|
|
1763
1926
|
} else {
|
|
1764
|
-
console.log(
|
|
1927
|
+
console.log(chalk14.green(`\u2713 saved ${result.path}`));
|
|
1765
1928
|
}
|
|
1766
1929
|
process.exitCode = 0;
|
|
1767
1930
|
} catch (err) {
|
|
@@ -1778,7 +1941,7 @@ async function buildsCancel(id, opts, deps) {
|
|
|
1778
1941
|
if (opts.json) {
|
|
1779
1942
|
console.log(JSON.stringify(build));
|
|
1780
1943
|
} else {
|
|
1781
|
-
console.log(`${
|
|
1944
|
+
console.log(`${chalk14.cyan(build.id)} ${build.status}`);
|
|
1782
1945
|
}
|
|
1783
1946
|
process.exitCode = 0;
|
|
1784
1947
|
} catch (err) {
|
|
@@ -1795,7 +1958,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1795
1958
|
if (opts.json) {
|
|
1796
1959
|
console.log(JSON.stringify(result));
|
|
1797
1960
|
} else {
|
|
1798
|
-
console.log(
|
|
1961
|
+
console.log(chalk14.green(`\u2713 deleted artifact for ${id}`));
|
|
1799
1962
|
}
|
|
1800
1963
|
process.exitCode = 0;
|
|
1801
1964
|
} catch (err) {
|
|
@@ -1806,11 +1969,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1806
1969
|
function printError(opts, err) {
|
|
1807
1970
|
const message = err instanceof Error ? err.message : String(err);
|
|
1808
1971
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1809
|
-
console.error(
|
|
1972
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1810
1973
|
}
|
|
1811
1974
|
|
|
1812
1975
|
// src/commands/submit.ts
|
|
1813
|
-
import
|
|
1976
|
+
import chalk15 from "chalk";
|
|
1814
1977
|
function defaultDeps2(client) {
|
|
1815
1978
|
return {
|
|
1816
1979
|
createSubmission: client.createSubmission.bind(client),
|
|
@@ -1834,7 +1997,7 @@ function submitConfigFor(cwd, platform) {
|
|
|
1834
1997
|
cfg = loadDevConfig(cwd);
|
|
1835
1998
|
} catch (err) {
|
|
1836
1999
|
console.error(
|
|
1837
|
-
|
|
2000
|
+
chalk15.yellow(
|
|
1838
2001
|
`warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
|
|
1839
2002
|
)
|
|
1840
2003
|
);
|
|
@@ -1982,12 +2145,12 @@ async function submitCommand(opts, deps) {
|
|
|
1982
2145
|
return;
|
|
1983
2146
|
}
|
|
1984
2147
|
if (!opts.json) {
|
|
1985
|
-
console.error(` ${
|
|
2148
|
+
console.error(` ${chalk15.cyan(sub.id)} ${chalk15.dim(`[${sub.platform}]`)}`);
|
|
1986
2149
|
}
|
|
1987
2150
|
const final = await followSubmission(sub, opts, d);
|
|
1988
2151
|
if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
|
|
1989
2152
|
console.error(
|
|
1990
|
-
|
|
2153
|
+
chalk15.dim("release status: draft \u2014 activate it from the Play Console when ready")
|
|
1991
2154
|
);
|
|
1992
2155
|
}
|
|
1993
2156
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
@@ -2018,11 +2181,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
|
2018
2181
|
});
|
|
2019
2182
|
emit(opts.json, { event: "preflight", ...result });
|
|
2020
2183
|
if (!opts.json) {
|
|
2021
|
-
const mark = result.ready ?
|
|
2184
|
+
const mark = result.ready ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
2022
2185
|
console.error(`${mark} ${result.message}`);
|
|
2023
2186
|
if (result.ready) {
|
|
2024
2187
|
console.error(
|
|
2025
|
-
|
|
2188
|
+
chalk15.dim(
|
|
2026
2189
|
` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
|
|
2027
2190
|
)
|
|
2028
2191
|
);
|
|
@@ -2056,7 +2219,7 @@ async function linkedIdentifier(platform, opts, deps) {
|
|
|
2056
2219
|
}
|
|
2057
2220
|
if (!app.package_name) return void 0;
|
|
2058
2221
|
if (!opts.json) {
|
|
2059
|
-
console.error(
|
|
2222
|
+
console.error(chalk15.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
|
|
2060
2223
|
}
|
|
2061
2224
|
return app.package_name;
|
|
2062
2225
|
}
|
|
@@ -2097,14 +2260,14 @@ async function followSubmission(created, opts, deps) {
|
|
|
2097
2260
|
if (opts.json) {
|
|
2098
2261
|
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
2099
2262
|
} else {
|
|
2100
|
-
console.error(
|
|
2263
|
+
console.error(chalk15.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
2101
2264
|
}
|
|
2102
2265
|
} else if (log.status === "running" && !printedRunning) {
|
|
2103
2266
|
printedRunning = true;
|
|
2104
2267
|
if (opts.json) {
|
|
2105
2268
|
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
2106
2269
|
} else {
|
|
2107
|
-
console.error(`${
|
|
2270
|
+
console.error(`${chalk15.cyan("\u25B6")} submitting`);
|
|
2108
2271
|
}
|
|
2109
2272
|
}
|
|
2110
2273
|
if (log.content) {
|
|
@@ -2146,7 +2309,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2146
2309
|
});
|
|
2147
2310
|
if (!opts.json) {
|
|
2148
2311
|
const ok = final.status === "succeeded";
|
|
2149
|
-
const mark = ok ?
|
|
2312
|
+
const mark = ok ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
2150
2313
|
const duration = formatDuration(final.started_at, final.finished_at);
|
|
2151
2314
|
let line = `${mark} ${final.status}`;
|
|
2152
2315
|
if (duration) line += ` (${duration})`;
|
|
@@ -2158,11 +2321,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2158
2321
|
function printError2(opts, err) {
|
|
2159
2322
|
const message = err instanceof Error ? err.message : String(err);
|
|
2160
2323
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2161
|
-
console.error(
|
|
2324
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2162
2325
|
}
|
|
2163
2326
|
|
|
2164
2327
|
// src/commands/versions.ts
|
|
2165
|
-
import
|
|
2328
|
+
import chalk16 from "chalk";
|
|
2166
2329
|
function defaultDeps3(client) {
|
|
2167
2330
|
return {
|
|
2168
2331
|
listAppVersions: client.listAppVersions.bind(client),
|
|
@@ -2212,7 +2375,7 @@ async function versionsSet(buildNumber, opts, deps) {
|
|
|
2212
2375
|
console.log(JSON.stringify(version));
|
|
2213
2376
|
} else {
|
|
2214
2377
|
console.log(
|
|
2215
|
-
|
|
2378
|
+
chalk16.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk16.dim(
|
|
2216
2379
|
" \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
|
|
2217
2380
|
)
|
|
2218
2381
|
);
|
|
@@ -2232,18 +2395,18 @@ function formatVersionsHuman(versions2) {
|
|
|
2232
2395
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2233
2396
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2234
2397
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2235
|
-
const lines = [
|
|
2398
|
+
const lines = [chalk16.bold(join4(padCells(HEADERS2)))];
|
|
2236
2399
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2237
2400
|
return lines.join("\n") + "\n";
|
|
2238
2401
|
}
|
|
2239
2402
|
function printError3(opts, err) {
|
|
2240
2403
|
const message = err instanceof Error ? err.message : String(err);
|
|
2241
2404
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2242
|
-
console.error(
|
|
2405
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2243
2406
|
}
|
|
2244
2407
|
|
|
2245
2408
|
// src/commands/whoami.ts
|
|
2246
|
-
import
|
|
2409
|
+
import chalk17 from "chalk";
|
|
2247
2410
|
function defaultDeps4(opts) {
|
|
2248
2411
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2249
2412
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -2255,6 +2418,15 @@ function effectiveRole(role, tokenRole) {
|
|
|
2255
2418
|
if (!tokenRole) return role;
|
|
2256
2419
|
return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
|
|
2257
2420
|
}
|
|
2421
|
+
function describeExpiry(expiresAt, now = /* @__PURE__ */ new Date()) {
|
|
2422
|
+
if (!expiresAt) return "never";
|
|
2423
|
+
const when = new Date(expiresAt);
|
|
2424
|
+
if (Number.isNaN(when.getTime())) return expiresAt;
|
|
2425
|
+
const date = when.toISOString().slice(0, 10);
|
|
2426
|
+
const days = Math.ceil((when.getTime() - now.getTime()) / 864e5);
|
|
2427
|
+
if (days <= 0) return `${date} (expired)`;
|
|
2428
|
+
return `${date} (in ${days} day${days === 1 ? "" : "s"})`;
|
|
2429
|
+
}
|
|
2258
2430
|
async function whoAmI(opts, deps) {
|
|
2259
2431
|
const d = deps ?? defaultDeps4(opts);
|
|
2260
2432
|
try {
|
|
@@ -2265,27 +2437,33 @@ async function whoAmI(opts, deps) {
|
|
|
2265
2437
|
process.exitCode = 0;
|
|
2266
2438
|
return;
|
|
2267
2439
|
}
|
|
2268
|
-
console.log(`${
|
|
2440
|
+
console.log(`${chalk17.bold("Logged in as")} ${me.email}`);
|
|
2269
2441
|
if (me.memberships.length === 1) {
|
|
2270
|
-
console.log(`${
|
|
2442
|
+
console.log(`${chalk17.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
2271
2443
|
} else if (me.memberships.length > 1) {
|
|
2272
|
-
console.log(
|
|
2444
|
+
console.log(chalk17.bold("Organizations"));
|
|
2273
2445
|
for (const m of me.memberships) {
|
|
2274
2446
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
2275
2447
|
}
|
|
2276
2448
|
}
|
|
2277
|
-
console.log(`${
|
|
2278
|
-
console.log(`${
|
|
2279
|
-
console.log(`${
|
|
2449
|
+
console.log(`${chalk17.bold("Member role")} ${me.role ?? "\u2014"}`);
|
|
2450
|
+
console.log(`${chalk17.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
|
|
2451
|
+
console.log(`${chalk17.bold("Effective role")} ${effective ?? "\u2014"}`);
|
|
2452
|
+
if (me.token_access) {
|
|
2453
|
+
console.log(
|
|
2454
|
+
`${chalk17.bold("Access")} ${me.token_access === "ci" ? "CI only (upload builds, run tests, read results)" : "Full access"}`
|
|
2455
|
+
);
|
|
2456
|
+
console.log(`${chalk17.bold("Expires")} ${describeExpiry(me.token_expires_at)}`);
|
|
2457
|
+
}
|
|
2280
2458
|
if (me.is_superadmin) {
|
|
2281
|
-
console.log(
|
|
2459
|
+
console.log(chalk17.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
2282
2460
|
}
|
|
2283
|
-
if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
|
|
2461
|
+
if (me.role === "admin" && me.token_role && me.token_role !== "admin" && me.token_access !== "ci") {
|
|
2284
2462
|
console.log(
|
|
2285
|
-
|
|
2463
|
+
chalk17.yellow(
|
|
2286
2464
|
`
|
|
2287
|
-
Your account is admin, but this CLI token is ${me.token_role}. The effective role is the lower of the two, so admin-only commands (such as ${
|
|
2288
|
-
Create an admin API token in the dashboard and use ${
|
|
2465
|
+
Your account is admin, but this CLI token is ${me.token_role}. The effective role is the lower of the two, so admin-only commands (such as ${chalk17.bold("vibeview env set")}) will fail with a permissions error.
|
|
2466
|
+
Create an admin API token in the dashboard and use ${chalk17.bold("--token")} or ${chalk17.bold("VIBEVIEW_API_TOKEN")}.`
|
|
2289
2467
|
)
|
|
2290
2468
|
);
|
|
2291
2469
|
}
|
|
@@ -2295,7 +2473,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2295
2473
|
if (opts.json) {
|
|
2296
2474
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2297
2475
|
}
|
|
2298
|
-
console.error(
|
|
2476
|
+
console.error(chalk17.red(`error: ${message}`));
|
|
2299
2477
|
process.exitCode = errorExitCode(err);
|
|
2300
2478
|
}
|
|
2301
2479
|
}
|
|
@@ -2303,7 +2481,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2303
2481
|
// src/commands/env.ts
|
|
2304
2482
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
2305
2483
|
import { basename } from "path";
|
|
2306
|
-
import
|
|
2484
|
+
import chalk18 from "chalk";
|
|
2307
2485
|
import { password } from "@inquirer/prompts";
|
|
2308
2486
|
|
|
2309
2487
|
// src/lib/gitignore.ts
|
|
@@ -2359,7 +2537,7 @@ function defaultDeps5(client) {
|
|
|
2359
2537
|
isIgnored: (path) => isGitIgnored(path),
|
|
2360
2538
|
writeFile: writeSecureFile,
|
|
2361
2539
|
readFile: (path) => readFileAsync(path),
|
|
2362
|
-
warn: (message) => console.error(
|
|
2540
|
+
warn: (message) => console.error(chalk18.yellow(`warning: ${message}`))
|
|
2363
2541
|
};
|
|
2364
2542
|
}
|
|
2365
2543
|
async function envList(opts, deps) {
|
|
@@ -2391,7 +2569,7 @@ function formatEnvVarsHuman(variables) {
|
|
|
2391
2569
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2392
2570
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2393
2571
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2394
|
-
const lines = [
|
|
2572
|
+
const lines = [chalk18.bold(join4(padCells(HEADERS3)))];
|
|
2395
2573
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2396
2574
|
return lines.join("\n") + "\n";
|
|
2397
2575
|
}
|
|
@@ -2441,7 +2619,7 @@ function printSetResult(opts, result) {
|
|
|
2441
2619
|
if (opts.json) {
|
|
2442
2620
|
console.log(JSON.stringify(result));
|
|
2443
2621
|
} else {
|
|
2444
|
-
console.log(
|
|
2622
|
+
console.log(chalk18.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
2445
2623
|
}
|
|
2446
2624
|
}
|
|
2447
2625
|
async function envRm(name, opts, deps) {
|
|
@@ -2451,7 +2629,7 @@ async function envRm(name, opts, deps) {
|
|
|
2451
2629
|
if (opts.json) {
|
|
2452
2630
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2453
2631
|
} else {
|
|
2454
|
-
console.log(
|
|
2632
|
+
console.log(chalk18.green(`\u2713 removed ${name}`));
|
|
2455
2633
|
}
|
|
2456
2634
|
process.exitCode = 0;
|
|
2457
2635
|
} catch (err) {
|
|
@@ -2483,7 +2661,7 @@ async function envPull(opts, deps) {
|
|
|
2483
2661
|
if (opts.json) {
|
|
2484
2662
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
2485
2663
|
} else {
|
|
2486
|
-
console.log(
|
|
2664
|
+
console.log(chalk18.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
2487
2665
|
}
|
|
2488
2666
|
process.exitCode = 0;
|
|
2489
2667
|
} catch (err) {
|
|
@@ -2522,7 +2700,7 @@ function escapeEnvValue(value) {
|
|
|
2522
2700
|
function printError4(opts, err) {
|
|
2523
2701
|
const message = err instanceof Error ? err.message : String(err);
|
|
2524
2702
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2525
|
-
console.error(
|
|
2703
|
+
console.error(chalk18.red(`error: ${message}`));
|
|
2526
2704
|
}
|
|
2527
2705
|
|
|
2528
2706
|
// src/commands/credentials.ts
|
|
@@ -2534,7 +2712,7 @@ import {
|
|
|
2534
2712
|
} from "fs/promises";
|
|
2535
2713
|
import { mkdirSync } from "fs";
|
|
2536
2714
|
import { basename as basename2, extname, join } from "path";
|
|
2537
|
-
import
|
|
2715
|
+
import chalk19 from "chalk";
|
|
2538
2716
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
2539
2717
|
function clientFor2(opts) {
|
|
2540
2718
|
const { token, baseUrl } = resolveAuth(opts);
|
|
@@ -2607,7 +2785,7 @@ function defaultDeps6(client) {
|
|
|
2607
2785
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
2608
2786
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
2609
2787
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
2610
|
-
warn: (message) => console.error(
|
|
2788
|
+
warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
|
|
2611
2789
|
};
|
|
2612
2790
|
}
|
|
2613
2791
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -2676,7 +2854,7 @@ async function credentialsAdd(opts, deps) {
|
|
|
2676
2854
|
if (opts.json) {
|
|
2677
2855
|
console.log(JSON.stringify(result));
|
|
2678
2856
|
} else {
|
|
2679
|
-
console.log(
|
|
2857
|
+
console.log(chalk19.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
2680
2858
|
}
|
|
2681
2859
|
process.exitCode = 0;
|
|
2682
2860
|
} catch (err) {
|
|
@@ -2734,20 +2912,20 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2734
2912
|
}
|
|
2735
2913
|
if (opts.json) {
|
|
2736
2914
|
} else {
|
|
2737
|
-
console.log(
|
|
2915
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (android)`));
|
|
2738
2916
|
console.log("");
|
|
2739
|
-
console.log(` Keystore saved to: ${
|
|
2740
|
-
console.log(` Key alias: ${
|
|
2741
|
-
console.log(` Store password: ${
|
|
2742
|
-
console.log(` Key password: ${
|
|
2917
|
+
console.log(` Keystore saved to: ${chalk19.bold(savedTo)}`);
|
|
2918
|
+
console.log(` Key alias: ${chalk19.bold(result.key_alias)}`);
|
|
2919
|
+
console.log(` Store password: ${chalk19.bold(result.store_password)}`);
|
|
2920
|
+
console.log(` Key password: ${chalk19.bold(result.key_password)}`);
|
|
2743
2921
|
console.log("");
|
|
2744
2922
|
console.log(
|
|
2745
|
-
|
|
2923
|
+
chalk19.yellow(
|
|
2746
2924
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
2747
2925
|
)
|
|
2748
2926
|
);
|
|
2749
2927
|
console.log(
|
|
2750
|
-
|
|
2928
|
+
chalk19.yellow(
|
|
2751
2929
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
2752
2930
|
)
|
|
2753
2931
|
);
|
|
@@ -2773,19 +2951,19 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
2773
2951
|
if (opts.json) {
|
|
2774
2952
|
console.log(JSON.stringify(result));
|
|
2775
2953
|
} else {
|
|
2776
|
-
console.log(
|
|
2954
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
2777
2955
|
console.log("");
|
|
2778
|
-
console.log(` Scope: ${
|
|
2956
|
+
console.log(` Scope: ${chalk19.bold(result.app_public_id ?? "org-wide")}`);
|
|
2779
2957
|
const expires = formatExpires(result);
|
|
2780
2958
|
if (expires) {
|
|
2781
|
-
console.log(` Expires: ${
|
|
2959
|
+
console.log(` Expires: ${chalk19.bold(expires)}`);
|
|
2782
2960
|
}
|
|
2783
2961
|
console.log("");
|
|
2784
2962
|
console.log(
|
|
2785
2963
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
2786
2964
|
);
|
|
2787
2965
|
console.log(
|
|
2788
|
-
` Run ${
|
|
2966
|
+
` Run ${chalk19.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
2789
2967
|
);
|
|
2790
2968
|
}
|
|
2791
2969
|
process.exitCode = 0;
|
|
@@ -2851,17 +3029,17 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
2851
3029
|
await d.chmod(path, 384);
|
|
2852
3030
|
}
|
|
2853
3031
|
if (!opts.json) {
|
|
2854
|
-
console.log(
|
|
3032
|
+
console.log(chalk19.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
2855
3033
|
console.log("");
|
|
2856
|
-
console.log(` Saved to: ${
|
|
3034
|
+
console.log(` Saved to: ${chalk19.bold(savedTo)}`);
|
|
2857
3035
|
if (profileSavedTo !== null) {
|
|
2858
|
-
console.log(` Profile: ${
|
|
3036
|
+
console.log(` Profile: ${chalk19.bold(profileSavedTo)}`);
|
|
2859
3037
|
}
|
|
2860
3038
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
2861
|
-
console.log(` ${key}: ${
|
|
3039
|
+
console.log(` ${key}: ${chalk19.bold(value)}`);
|
|
2862
3040
|
}
|
|
2863
3041
|
console.log("");
|
|
2864
|
-
console.log(
|
|
3042
|
+
console.log(chalk19.yellow(" This download is recorded in your organization's audit log."));
|
|
2865
3043
|
}
|
|
2866
3044
|
process.exitCode = 0;
|
|
2867
3045
|
} catch (err) {
|
|
@@ -2892,13 +3070,13 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2892
3070
|
if (opts.json) {
|
|
2893
3071
|
console.log(JSON.stringify(result));
|
|
2894
3072
|
} else {
|
|
2895
|
-
console.log(
|
|
3073
|
+
console.log(chalk19.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2896
3074
|
console.log("");
|
|
2897
|
-
console.log(` Key ID: ${
|
|
2898
|
-
console.log(` Issuer ID: ${
|
|
3075
|
+
console.log(` Key ID: ${chalk19.bold(result.key_id)}`);
|
|
3076
|
+
console.log(` Issuer ID: ${chalk19.bold(result.issuer_id)}`);
|
|
2899
3077
|
console.log("");
|
|
2900
3078
|
console.log(
|
|
2901
|
-
|
|
3079
|
+
chalk19.yellow(
|
|
2902
3080
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2903
3081
|
)
|
|
2904
3082
|
);
|
|
@@ -2933,7 +3111,7 @@ function formatAscKeysHuman(keys) {
|
|
|
2933
3111
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2934
3112
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2935
3113
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2936
|
-
const lines = [
|
|
3114
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
2937
3115
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2938
3116
|
return lines.join("\n") + "\n";
|
|
2939
3117
|
}
|
|
@@ -2944,7 +3122,7 @@ async function ascKeyRm(name, opts, deps) {
|
|
|
2944
3122
|
if (opts.json) {
|
|
2945
3123
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2946
3124
|
} else {
|
|
2947
|
-
console.log(
|
|
3125
|
+
console.log(chalk19.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2948
3126
|
}
|
|
2949
3127
|
process.exitCode = 0;
|
|
2950
3128
|
} catch (err) {
|
|
@@ -2969,12 +3147,12 @@ async function playKeyAdd(opts, deps) {
|
|
|
2969
3147
|
if (opts.json) {
|
|
2970
3148
|
console.log(JSON.stringify(result));
|
|
2971
3149
|
} else {
|
|
2972
|
-
console.log(
|
|
3150
|
+
console.log(chalk19.green(`\u2713 added Google Play key ${result.name}`));
|
|
2973
3151
|
console.log("");
|
|
2974
|
-
console.log(` Service account: ${
|
|
3152
|
+
console.log(` Service account: ${chalk19.bold(result.client_email)}`);
|
|
2975
3153
|
console.log("");
|
|
2976
3154
|
console.log(
|
|
2977
|
-
|
|
3155
|
+
chalk19.yellow(
|
|
2978
3156
|
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2979
3157
|
)
|
|
2980
3158
|
);
|
|
@@ -3009,7 +3187,7 @@ function formatPlayKeysHuman(keys) {
|
|
|
3009
3187
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3010
3188
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3011
3189
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3012
|
-
const lines = [
|
|
3190
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
3013
3191
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3014
3192
|
return lines.join("\n") + "\n";
|
|
3015
3193
|
}
|
|
@@ -3020,7 +3198,7 @@ async function playKeyRm(name, opts, deps) {
|
|
|
3020
3198
|
if (opts.json) {
|
|
3021
3199
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3022
3200
|
} else {
|
|
3023
|
-
console.log(
|
|
3201
|
+
console.log(chalk19.green(`\u2713 removed Google Play key ${name}`));
|
|
3024
3202
|
}
|
|
3025
3203
|
process.exitCode = 0;
|
|
3026
3204
|
} catch (err) {
|
|
@@ -3089,7 +3267,7 @@ function formatCredentialsHuman(credentials2) {
|
|
|
3089
3267
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3090
3268
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3091
3269
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3092
|
-
const lines = [
|
|
3270
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
3093
3271
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3094
3272
|
return lines.join("\n") + "\n";
|
|
3095
3273
|
}
|
|
@@ -3100,7 +3278,7 @@ async function credentialsRm(name, opts, deps) {
|
|
|
3100
3278
|
if (opts.json) {
|
|
3101
3279
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3102
3280
|
} else {
|
|
3103
|
-
console.log(
|
|
3281
|
+
console.log(chalk19.green(`\u2713 removed ${name}`));
|
|
3104
3282
|
}
|
|
3105
3283
|
process.exitCode = 0;
|
|
3106
3284
|
} catch (err) {
|
|
@@ -3112,16 +3290,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3112
3290
|
const message = err instanceof Error ? err.message : String(err);
|
|
3113
3291
|
if (opts.json) {
|
|
3114
3292
|
console.log(JSON.stringify({ event: "error", message }));
|
|
3115
|
-
console.error(
|
|
3293
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3116
3294
|
return;
|
|
3117
3295
|
}
|
|
3118
|
-
console.error(
|
|
3296
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3119
3297
|
console.error(
|
|
3120
|
-
|
|
3298
|
+
chalk19.red(
|
|
3121
3299
|
`The keystore could NOT be saved locally, but the credential "${result.name}" WAS created server-side and its key material can never be re-fetched.`
|
|
3122
3300
|
)
|
|
3123
3301
|
);
|
|
3124
|
-
console.error(
|
|
3302
|
+
console.error(chalk19.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
3125
3303
|
console.error("");
|
|
3126
3304
|
console.error(` Key alias: ${result.key_alias}`);
|
|
3127
3305
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -3135,11 +3313,11 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3135
3313
|
function printError5(opts, err) {
|
|
3136
3314
|
const message = err instanceof Error ? err.message : String(err);
|
|
3137
3315
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3138
|
-
console.error(
|
|
3316
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3139
3317
|
}
|
|
3140
3318
|
|
|
3141
3319
|
// src/commands/webhooks.ts
|
|
3142
|
-
import
|
|
3320
|
+
import chalk20 from "chalk";
|
|
3143
3321
|
function clientFor3(opts) {
|
|
3144
3322
|
const { token, baseUrl } = resolveAuth(opts);
|
|
3145
3323
|
return new ApiClient(baseUrl, token);
|
|
@@ -3170,14 +3348,14 @@ async function webhooksAdd(opts, deps) {
|
|
|
3170
3348
|
if (opts.json) {
|
|
3171
3349
|
console.log(JSON.stringify(result));
|
|
3172
3350
|
} else {
|
|
3173
|
-
console.log(
|
|
3351
|
+
console.log(chalk20.green(`\u2713 added ${result.id} ${result.url}`));
|
|
3174
3352
|
console.log("");
|
|
3175
|
-
console.log(` Signing secret: ${
|
|
3353
|
+
console.log(` Signing secret: ${chalk20.bold(result.secret)}`);
|
|
3176
3354
|
if (opts.secret !== void 0) {
|
|
3177
|
-
console.log(
|
|
3355
|
+
console.log(chalk20.yellow(" (This is the secret you provided.)"));
|
|
3178
3356
|
}
|
|
3179
3357
|
console.log(
|
|
3180
|
-
|
|
3358
|
+
chalk20.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
3181
3359
|
);
|
|
3182
3360
|
}
|
|
3183
3361
|
process.exitCode = 0;
|
|
@@ -3208,7 +3386,7 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
3208
3386
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3209
3387
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3210
3388
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3211
|
-
const lines = [
|
|
3389
|
+
const lines = [chalk20.bold(join4(padCells(HEADERS3)))];
|
|
3212
3390
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3213
3391
|
return lines.join("\n") + "\n";
|
|
3214
3392
|
}
|
|
@@ -3234,7 +3412,7 @@ async function webhooksRm(id, opts, deps) {
|
|
|
3234
3412
|
if (opts.json) {
|
|
3235
3413
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
3236
3414
|
} else {
|
|
3237
|
-
console.log(
|
|
3415
|
+
console.log(chalk20.green(`\u2713 removed ${id}`));
|
|
3238
3416
|
}
|
|
3239
3417
|
process.exitCode = 0;
|
|
3240
3418
|
} catch (err) {
|
|
@@ -3267,13 +3445,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3267
3445
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
3268
3446
|
if (ld.status === "succeeded") {
|
|
3269
3447
|
if (!opts.json) {
|
|
3270
|
-
console.log(
|
|
3448
|
+
console.log(chalk20.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
3271
3449
|
}
|
|
3272
3450
|
process.exitCode = 0;
|
|
3273
3451
|
} else {
|
|
3274
3452
|
if (!opts.json) {
|
|
3275
3453
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
3276
|
-
console.error(
|
|
3454
|
+
console.error(chalk20.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
|
|
3277
3455
|
}
|
|
3278
3456
|
process.exitCode = 1;
|
|
3279
3457
|
}
|
|
@@ -3282,7 +3460,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3282
3460
|
}
|
|
3283
3461
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
3284
3462
|
console.error(
|
|
3285
|
-
|
|
3463
|
+
chalk20.yellow(
|
|
3286
3464
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
3287
3465
|
)
|
|
3288
3466
|
);
|
|
@@ -3295,7 +3473,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3295
3473
|
function printError6(opts, err) {
|
|
3296
3474
|
const message = err instanceof Error ? err.message : String(err);
|
|
3297
3475
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3298
|
-
console.error(
|
|
3476
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3299
3477
|
}
|
|
3300
3478
|
|
|
3301
3479
|
// src/commands/list-devices.ts
|
|
@@ -3393,7 +3571,7 @@ import { dirname, join as join2 } from "path";
|
|
|
3393
3571
|
import { fileURLToPath } from "url";
|
|
3394
3572
|
import { spawnSync } from "child_process";
|
|
3395
3573
|
import { confirm } from "@inquirer/prompts";
|
|
3396
|
-
import
|
|
3574
|
+
import chalk21 from "chalk";
|
|
3397
3575
|
function resolveShippedSkillPath(fromDir) {
|
|
3398
3576
|
for (const rel of ["..", join2("..", "..")]) {
|
|
3399
3577
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -3421,23 +3599,23 @@ function registerMcp() {
|
|
|
3421
3599
|
encoding: "utf8"
|
|
3422
3600
|
});
|
|
3423
3601
|
if (res.status === 0) {
|
|
3424
|
-
console.log(
|
|
3602
|
+
console.log(chalk21.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
3425
3603
|
return;
|
|
3426
3604
|
}
|
|
3427
3605
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
3428
3606
|
if (out.includes("already exists")) {
|
|
3429
|
-
console.log(
|
|
3607
|
+
console.log(chalk21.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
3430
3608
|
return;
|
|
3431
3609
|
}
|
|
3432
|
-
console.error(
|
|
3433
|
-
console.error(
|
|
3610
|
+
console.error(chalk21.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
3611
|
+
console.error(chalk21.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
3434
3612
|
}
|
|
3435
3613
|
async function agentSetup(opts) {
|
|
3436
3614
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3437
3615
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
3438
3616
|
if (!shipped) {
|
|
3439
3617
|
console.error(
|
|
3440
|
-
|
|
3618
|
+
chalk21.red(
|
|
3441
3619
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
3442
3620
|
)
|
|
3443
3621
|
);
|
|
@@ -3446,9 +3624,9 @@ async function agentSetup(opts) {
|
|
|
3446
3624
|
}
|
|
3447
3625
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
3448
3626
|
const result = installSkill(shipped, targetDir);
|
|
3449
|
-
console.log(
|
|
3627
|
+
console.log(chalk21.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
3450
3628
|
console.log(
|
|
3451
|
-
|
|
3629
|
+
chalk21.dim(
|
|
3452
3630
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
3453
3631
|
)
|
|
3454
3632
|
);
|
|
@@ -3456,7 +3634,7 @@ async function agentSetup(opts) {
|
|
|
3456
3634
|
if (!claudeCliAvailable()) {
|
|
3457
3635
|
if (opts.mcp === true) {
|
|
3458
3636
|
console.error(
|
|
3459
|
-
|
|
3637
|
+
chalk21.yellow(
|
|
3460
3638
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
3461
3639
|
)
|
|
3462
3640
|
);
|
|
@@ -3470,7 +3648,7 @@ async function agentSetup(opts) {
|
|
|
3470
3648
|
}
|
|
3471
3649
|
console.log(
|
|
3472
3650
|
`
|
|
3473
|
-
Suggested line for your project's ${
|
|
3651
|
+
Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bold(
|
|
3474
3652
|
"AGENTS.md"
|
|
3475
3653
|
)} for other agents):
|
|
3476
3654
|
`
|
|
@@ -3478,7 +3656,7 @@ Suggested line for your project's ${chalk20.bold("CLAUDE.md")} (or ${chalk20.bol
|
|
|
3478
3656
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
3479
3657
|
`);
|
|
3480
3658
|
console.log(
|
|
3481
|
-
|
|
3659
|
+
chalk21.dim(
|
|
3482
3660
|
"Other agents (Cursor, Copilot, Codex, ...): install the same skill from GitHub with\n npx skills add vibeview/skills\nor point them at the skill file above, or add the MCP server per client \u2014\nsee https://vibeview.io/docs/agent-control"
|
|
3483
3661
|
)
|
|
3484
3662
|
);
|
|
@@ -3613,6 +3791,7 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
|
|
|
3613
3791
|
}
|
|
3614
3792
|
try {
|
|
3615
3793
|
parseRunPlatform(opts.platform);
|
|
3794
|
+
refusePlatformWithApp(opts);
|
|
3616
3795
|
} catch (err) {
|
|
3617
3796
|
process.stderr.write(`error: ${err.message}
|
|
3618
3797
|
`);
|
|
@@ -3681,6 +3860,7 @@ program.command("run-test <test-id>").description("Run a single test case").opti
|
|
|
3681
3860
|
}
|
|
3682
3861
|
try {
|
|
3683
3862
|
parseRunPlatform(opts.platform);
|
|
3863
|
+
refusePlatformWithApp(opts);
|
|
3684
3864
|
} catch (err) {
|
|
3685
3865
|
process.stderr.write(`error: ${err.message}
|
|
3686
3866
|
`);
|
|
@@ -3899,7 +4079,7 @@ program.command("dev-reload").description(
|
|
|
3899
4079
|
program.command("mcp").description(
|
|
3900
4080
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3901
4081
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3902
|
-
const { runMcpServer } = await import("./server-
|
|
4082
|
+
const { runMcpServer } = await import("./server-FJSBWEHZ.js");
|
|
3903
4083
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
3904
4084
|
});
|
|
3905
4085
|
program.command("agent-setup").description(
|