vibeview 0.30.0 → 0.31.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
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
readDevState,
|
|
24
24
|
readMetroState,
|
|
25
25
|
runAgentVerb
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-P6UIJALK.js";
|
|
27
27
|
import {
|
|
28
28
|
ApiClient,
|
|
29
29
|
ApiStatusError,
|
|
@@ -51,7 +51,7 @@ import { createRequire } from "module";
|
|
|
51
51
|
import { Command, Option } from "commander";
|
|
52
52
|
|
|
53
53
|
// src/commands/run-suite.ts
|
|
54
|
-
import
|
|
54
|
+
import chalk4 from "chalk";
|
|
55
55
|
|
|
56
56
|
// src/lib/poll.ts
|
|
57
57
|
import chalk2 from "chalk";
|
|
@@ -145,6 +145,10 @@ function formatHuman(result, verbose) {
|
|
|
145
145
|
lines.push(`
|
|
146
146
|
${chalk.red("Error:")} ${result.error_message}`);
|
|
147
147
|
}
|
|
148
|
+
if (result.build_warning && result.status !== "passed") {
|
|
149
|
+
lines.push(`
|
|
150
|
+
${chalk.yellow("Note:")} ${result.build_warning}`);
|
|
151
|
+
}
|
|
148
152
|
lines.push("");
|
|
149
153
|
return lines.join("\n");
|
|
150
154
|
}
|
|
@@ -174,9 +178,16 @@ function formatJunit(result) {
|
|
|
174
178
|
lines.push(
|
|
175
179
|
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
176
180
|
);
|
|
177
|
-
if (result.url) {
|
|
181
|
+
if (result.url || result.build_warning) {
|
|
178
182
|
lines.push(" <properties>");
|
|
179
|
-
|
|
183
|
+
if (result.url) {
|
|
184
|
+
lines.push(` <property name="vibeview.run_url" value="${escapeXml(result.url)}" />`);
|
|
185
|
+
}
|
|
186
|
+
if (result.build_warning) {
|
|
187
|
+
lines.push(
|
|
188
|
+
` <property name="vibeview.build_warning" value="${escapeXml(result.build_warning)}" />`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
180
191
|
lines.push(" </properties>");
|
|
181
192
|
}
|
|
182
193
|
for (const tr of cases) {
|
|
@@ -225,46 +236,97 @@ function formatJunitError(name, message, url) {
|
|
|
225
236
|
return lines.join("\n");
|
|
226
237
|
}
|
|
227
238
|
var MULTI_TERMINAL = /* @__PURE__ */ new Set(["passed", "failed", "error", "cancelled"]);
|
|
239
|
+
function caseLines(c, classname) {
|
|
240
|
+
const name = escapeXml(c.name);
|
|
241
|
+
const head = ` <testcase name="${name}" classname="${classname}" time="${c.time}"`;
|
|
242
|
+
const out = c.url ? ` <system-out>${escapeXml(c.url)}</system-out>` : null;
|
|
243
|
+
if (c.verdict === "passed" && !out) return [`${head} />`];
|
|
244
|
+
const lines = [`${head}>`];
|
|
245
|
+
const msg = escapeXml(c.message ?? "");
|
|
246
|
+
if (c.verdict === "failure") {
|
|
247
|
+
lines.push(` <failure message="${msg}" type="TestFailure">${msg}</failure>`);
|
|
248
|
+
} else if (c.verdict === "error") {
|
|
249
|
+
lines.push(` <error message="${msg}" type="InfraError">${msg}</error>`);
|
|
250
|
+
} else if (c.verdict === "skipped") {
|
|
251
|
+
lines.push(` <skipped message="${msg}" />`);
|
|
252
|
+
}
|
|
253
|
+
if (out) lines.push(out);
|
|
254
|
+
lines.push(" </testcase>");
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
228
257
|
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);
|
|
258
|
+
const unfinished = (status) => opts.timedOut !== void 0 && !MULTI_TERMINAL.has(status ?? "");
|
|
235
259
|
const timestamp = result.started_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
260
|
+
const suites = result.suite_runs.map((sr) => {
|
|
261
|
+
const suiteName = sr.suite_name || sr.suite_id;
|
|
262
|
+
const cases = (sr.case_runs ?? []).map((c) => {
|
|
263
|
+
const name = c.case_name || c.run_id;
|
|
264
|
+
const time = c.duration_ms ? (c.duration_ms / 1e3).toFixed(1) : "0";
|
|
265
|
+
const base = { name, time, url: c.url || void 0 };
|
|
266
|
+
switch (c.status) {
|
|
267
|
+
case "passed":
|
|
268
|
+
return { ...base, verdict: "passed" };
|
|
269
|
+
case "failed":
|
|
270
|
+
return {
|
|
271
|
+
...base,
|
|
272
|
+
verdict: "failure",
|
|
273
|
+
message: c.error_message || "Test failed"
|
|
274
|
+
};
|
|
275
|
+
case "error":
|
|
276
|
+
return {
|
|
277
|
+
...base,
|
|
278
|
+
verdict: "error",
|
|
279
|
+
message: c.error_message || "Infrastructure error"
|
|
280
|
+
};
|
|
281
|
+
case "cancelled":
|
|
282
|
+
return { ...base, verdict: "skipped", message: c.error_message || "Cancelled" };
|
|
283
|
+
default:
|
|
284
|
+
return unfinished(c.status) ? {
|
|
285
|
+
...base,
|
|
286
|
+
verdict: "error",
|
|
287
|
+
message: `${opts.timedOut} (case was ${c.status ?? "not started"})`
|
|
288
|
+
} : { ...base, verdict: "skipped", message: "Not run" };
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
const suiteError = unfinished(sr.status) ? `${opts.timedOut} (suite was ${sr.status})` : sr.status === "error" ? sr.error_message || "Suite errored" : null;
|
|
292
|
+
if (suiteError && !cases.some((c) => c.verdict === "error")) {
|
|
293
|
+
cases.push({ name: suiteName, time: "0", verdict: "error", message: suiteError });
|
|
294
|
+
}
|
|
295
|
+
const legacy = sr.case_runs === void 0;
|
|
296
|
+
const tests2 = legacy ? sr.passed + sr.failed + (suiteError ? 1 : 0) : cases.length;
|
|
297
|
+
const failures = legacy ? sr.failed : cases.filter((c) => c.verdict === "failure").length;
|
|
298
|
+
const errors2 = cases.filter((c) => c.verdict === "error").length;
|
|
299
|
+
const skipped = cases.filter((c) => c.verdict === "skipped").length;
|
|
300
|
+
return { sr, suiteName, cases, tests: tests2, failures, errors: errors2, skipped };
|
|
301
|
+
});
|
|
302
|
+
const noSuites = opts.timedOut !== void 0 && result.suite_runs.length === 0;
|
|
303
|
+
const sum = (key) => suites.reduce((n, s) => n + s[key], 0);
|
|
304
|
+
const tests = sum("tests") + (noSuites ? 1 : 0);
|
|
305
|
+
const errors = sum("errors") + (noSuites ? 1 : 0);
|
|
236
306
|
const lines = [];
|
|
237
307
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
238
308
|
lines.push(
|
|
239
|
-
`<testsuites name="vibeview-multi" tests="${tests}" failures="${failures}" errors="${
|
|
309
|
+
`<testsuites name="vibeview-multi" tests="${tests}" failures="${sum("failures")}" errors="${errors}" skipped="${sum("skipped")}">`
|
|
240
310
|
);
|
|
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;
|
|
311
|
+
for (const s of suites) {
|
|
312
|
+
const suiteName = escapeXml(s.suiteName);
|
|
246
313
|
lines.push(
|
|
247
|
-
` <testsuite name="${suiteName}" tests="${
|
|
314
|
+
` <testsuite name="${suiteName}" tests="${s.tests}" failures="${s.failures}" errors="${s.errors}" skipped="${s.skipped}" timestamp="${timestamp}">`
|
|
248
315
|
);
|
|
249
|
-
if (sr.url) {
|
|
316
|
+
if (s.sr.url) {
|
|
250
317
|
lines.push(" <properties>");
|
|
251
|
-
lines.push(` <property name="vibeview.run_url" value="${escapeXml(sr.url)}" />`);
|
|
318
|
+
lines.push(` <property name="vibeview.run_url" value="${escapeXml(s.sr.url)}" />`);
|
|
252
319
|
lines.push(" </properties>");
|
|
253
320
|
}
|
|
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
|
-
}
|
|
321
|
+
for (const c of s.cases) lines.push(...caseLines(c, suiteName));
|
|
260
322
|
lines.push(" </testsuite>");
|
|
261
323
|
}
|
|
262
324
|
if (noSuites) {
|
|
263
|
-
const
|
|
325
|
+
const name = "vibeview run-suites";
|
|
264
326
|
lines.push(
|
|
265
|
-
` <testsuite name="
|
|
327
|
+
` <testsuite name="${name}" tests="1" failures="0" errors="1" skipped="0" timestamp="${timestamp}">`
|
|
266
328
|
);
|
|
267
|
-
lines.push(
|
|
329
|
+
lines.push(...caseLines({ name, time: "0", verdict: "error", message: opts.timedOut }, name));
|
|
268
330
|
lines.push(" </testsuite>");
|
|
269
331
|
}
|
|
270
332
|
lines.push("</testsuites>");
|
|
@@ -572,6 +634,7 @@ function mapExitCode(status) {
|
|
|
572
634
|
}
|
|
573
635
|
|
|
574
636
|
// src/lib/run-target.ts
|
|
637
|
+
import chalk3 from "chalk";
|
|
575
638
|
var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
576
639
|
function parseRunPlatform(value) {
|
|
577
640
|
if (value === void 0) return void 0;
|
|
@@ -580,6 +643,18 @@ function parseRunPlatform(value) {
|
|
|
580
643
|
}
|
|
581
644
|
return value;
|
|
582
645
|
}
|
|
646
|
+
function refusePlatformWithApp(options) {
|
|
647
|
+
if (options.platform && options.app) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
"--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."
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function printBuildWarning(warning, label) {
|
|
654
|
+
if (!warning) return;
|
|
655
|
+
const prefix = label ? `warning (${label}):` : "warning:";
|
|
656
|
+
process.stderr.write(chalk3.yellow(`${prefix} ${warning}`) + "\n");
|
|
657
|
+
}
|
|
583
658
|
function describeRunTarget(response, options) {
|
|
584
659
|
const asked = Boolean(options.platform || options.app);
|
|
585
660
|
if (!response.app_id) {
|
|
@@ -668,12 +743,13 @@ ${signal} received.
|
|
|
668
743
|
const target = describeRunTarget(response, options);
|
|
669
744
|
spinner.stop();
|
|
670
745
|
process.stderr.write(
|
|
671
|
-
`${
|
|
672
|
-
` + (target ? `${
|
|
673
|
-
` : "") + `${
|
|
674
|
-
` + (sessionId ? `${
|
|
746
|
+
`${chalk4.dim("Suite:")} ${suiteId}
|
|
747
|
+
` + (target ? `${chalk4.dim("App:")} ${target}
|
|
748
|
+
` : "") + `${chalk4.dim("Run:")} ${response.run_id}
|
|
749
|
+
` + (sessionId ? `${chalk4.dim("Session:")} ${sessionId}
|
|
675
750
|
` : "")
|
|
676
751
|
);
|
|
752
|
+
printBuildWarning(response.build_warning);
|
|
677
753
|
spinner.start("Waiting for device...");
|
|
678
754
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
679
755
|
const result = await pollUntilComplete(
|
|
@@ -691,7 +767,10 @@ ${signal} received.
|
|
|
691
767
|
process.stdin.setRawMode(false);
|
|
692
768
|
process.stdin.pause();
|
|
693
769
|
}
|
|
694
|
-
outputResult(
|
|
770
|
+
outputResult(
|
|
771
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
772
|
+
options
|
|
773
|
+
);
|
|
695
774
|
process.exit(mapExitCode(result.status));
|
|
696
775
|
} catch (err) {
|
|
697
776
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -717,7 +796,7 @@ ${signal} received.
|
|
|
717
796
|
}
|
|
718
797
|
|
|
719
798
|
// src/commands/run-suites.ts
|
|
720
|
-
import
|
|
799
|
+
import chalk5 from "chalk";
|
|
721
800
|
var AGGREGATE_TIMEOUT_CAP_S = 7200;
|
|
722
801
|
async function runSuites(opts) {
|
|
723
802
|
if (!opts.tag || opts.tag.length === 0) {
|
|
@@ -792,13 +871,13 @@ ${signal} received.
|
|
|
792
871
|
const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
|
|
793
872
|
if (opts.output === "json") printJsonError(noMatch);
|
|
794
873
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", noMatch));
|
|
795
|
-
process.stderr.write(
|
|
874
|
+
process.stderr.write(chalk5.yellow(`${noMatch}
|
|
796
875
|
`));
|
|
797
876
|
process.exit(EXIT_INFRA_ERROR);
|
|
798
877
|
}
|
|
799
878
|
if (opts.output === "json") printJsonError(err);
|
|
800
879
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", msg));
|
|
801
|
-
process.stderr.write(
|
|
880
|
+
process.stderr.write(chalk5.red(`Failed to start: ${msg}
|
|
802
881
|
`));
|
|
803
882
|
process.exit(EXIT_INFRA_ERROR);
|
|
804
883
|
}
|
|
@@ -807,16 +886,17 @@ ${signal} received.
|
|
|
807
886
|
const matchedNames = start.matched_suites?.map((s) => s.name) ?? [];
|
|
808
887
|
const totalMatched = start.matched_suite_ids.length;
|
|
809
888
|
process.stderr.write(
|
|
810
|
-
`${
|
|
811
|
-
${
|
|
812
|
-
${
|
|
889
|
+
`${chalk5.dim("Aggregate:")} ${start.aggregate_id}
|
|
890
|
+
${chalk5.dim("Tags:")} ${opts.tag.join(", ")} ${chalk5.dim("(" + (opts.match ?? "any") + ")")}
|
|
891
|
+
${chalk5.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u2014 " + matchedNames.join(", ") : ""}
|
|
813
892
|
`
|
|
814
893
|
);
|
|
894
|
+
for (const s of start.matched_suites ?? []) printBuildWarning(s.build_warning, s.name);
|
|
815
895
|
const excluded = start.excluded_suites ?? [];
|
|
816
896
|
if (excluded.length) {
|
|
817
897
|
const names = excluded.map((s) => s.name).join(", ");
|
|
818
898
|
process.stderr.write(
|
|
819
|
-
|
|
899
|
+
chalk5.dim(`Excluded ${excluded.length} suite(s) (platform mismatch): ${names}
|
|
820
900
|
`)
|
|
821
901
|
);
|
|
822
902
|
}
|
|
@@ -830,20 +910,28 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
830
910
|
const suiteStateBefore = /* @__PURE__ */ new Map();
|
|
831
911
|
const suiteStartedPrinted = /* @__PURE__ */ new Set();
|
|
832
912
|
let lastQueueReason = null;
|
|
913
|
+
let pending = [];
|
|
833
914
|
const print = (line) => {
|
|
915
|
+
pending.push(line);
|
|
916
|
+
};
|
|
917
|
+
const flush = (nextText) => {
|
|
918
|
+
const textChanged = nextText !== null && nextText !== spinner.text;
|
|
919
|
+
if (pending.length === 0 && !textChanged) return;
|
|
834
920
|
spinner.stop();
|
|
835
|
-
process.stderr.write(line + "\n");
|
|
836
|
-
|
|
921
|
+
for (const line of pending) process.stderr.write(line + "\n");
|
|
922
|
+
pending = [];
|
|
923
|
+
if (nextText !== null) spinner.start(nextText);
|
|
837
924
|
};
|
|
838
925
|
const TERMINAL = /* @__PURE__ */ new Set(["passed", "failed", "error", "cancelled"]);
|
|
839
926
|
let last = null;
|
|
840
927
|
const abandon = async (reason) => {
|
|
928
|
+
flush(null);
|
|
841
929
|
spinner.stop();
|
|
842
930
|
if (process.stdin.isTTY) {
|
|
843
931
|
process.stdin.setRawMode(false);
|
|
844
932
|
process.stdin.pause();
|
|
845
933
|
}
|
|
846
|
-
process.stderr.write(
|
|
934
|
+
process.stderr.write(chalk5.red(`${reason}
|
|
847
935
|
`));
|
|
848
936
|
process.stderr.write("Cancelling multi-suite run...\n");
|
|
849
937
|
try {
|
|
@@ -851,7 +939,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
851
939
|
process.stderr.write("Cancellation requested.\n");
|
|
852
940
|
} catch (err) {
|
|
853
941
|
const m = err instanceof Error ? err.message : String(err);
|
|
854
|
-
process.stderr.write(
|
|
942
|
+
process.stderr.write(chalk5.yellow(`Could not cancel the run: ${m}
|
|
855
943
|
`));
|
|
856
944
|
}
|
|
857
945
|
if (opts.output === "json") {
|
|
@@ -879,6 +967,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
879
967
|
return abandon(`Could not read the run's status: ${m}`);
|
|
880
968
|
}
|
|
881
969
|
let doneCount = 0;
|
|
970
|
+
let nextText;
|
|
882
971
|
let queuedChild;
|
|
883
972
|
for (const sr of last.suite_runs) {
|
|
884
973
|
const cur = sr.status;
|
|
@@ -888,17 +977,17 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
888
977
|
}
|
|
889
978
|
if (cur === "running" && !suiteStartedPrinted.has(sr.run_id)) {
|
|
890
979
|
suiteStartedPrinted.add(sr.run_id);
|
|
891
|
-
print(`${
|
|
980
|
+
print(`${chalk5.cyan("\u25B6")} ${chalk5.bold(sr.suite_name)} started`);
|
|
892
981
|
}
|
|
893
982
|
const wasTerminal = prev !== void 0 && TERMINAL.has(prev);
|
|
894
983
|
if (TERMINAL.has(cur) && !wasTerminal) {
|
|
895
|
-
const mark = cur === "passed" ?
|
|
896
|
-
const link = sr.url ?
|
|
984
|
+
const mark = cur === "passed" ? chalk5.green("\u2713") : cur === "cancelled" ? chalk5.yellow("\u26A0") : chalk5.red("\u2717");
|
|
985
|
+
const link = sr.url ? chalk5.dim(" " + sr.url) : "";
|
|
897
986
|
print(
|
|
898
|
-
`${mark} ${
|
|
987
|
+
`${mark} ${chalk5.bold(sr.suite_name)} ${cur}: ${sr.passed} passed, ${sr.failed} failed${link}`
|
|
899
988
|
);
|
|
900
989
|
if (sr.error_message) {
|
|
901
|
-
print(` ${
|
|
990
|
+
print(` ${chalk5.red(sr.error_message)}`);
|
|
902
991
|
}
|
|
903
992
|
}
|
|
904
993
|
suiteStateBefore.set(sr.run_id, cur);
|
|
@@ -911,16 +1000,20 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
911
1000
|
const why = queueReasonCopy(q.reason);
|
|
912
1001
|
if (why && why !== lastQueueReason) {
|
|
913
1002
|
print(
|
|
914
|
-
`${
|
|
1003
|
+
`${chalk5.yellow("\u23F3")} ${chalk5.bold(queuedChild.suite_name)} is waiting for a device: ${why}`
|
|
915
1004
|
);
|
|
916
1005
|
}
|
|
917
1006
|
lastQueueReason = why;
|
|
918
|
-
|
|
1007
|
+
nextText = `Queued \u2014 ${chalk5.bold(queuedChild.suite_name)} position ${q.position} for ${target} (${doneCount}/${totalMatched} suite(s) complete)`;
|
|
919
1008
|
} else {
|
|
920
1009
|
lastQueueReason = null;
|
|
921
|
-
|
|
1010
|
+
nextText = `Running suites \u2014 ${doneCount}/${totalMatched} complete...`;
|
|
1011
|
+
}
|
|
1012
|
+
if (TERMINAL.has(last.status)) {
|
|
1013
|
+
flush(null);
|
|
1014
|
+
break;
|
|
922
1015
|
}
|
|
923
|
-
|
|
1016
|
+
flush(nextText);
|
|
924
1017
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
925
1018
|
}
|
|
926
1019
|
if (!last || !TERMINAL.has(last.status)) {
|
|
@@ -934,7 +1027,9 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
934
1027
|
process.stdin.pause();
|
|
935
1028
|
}
|
|
936
1029
|
if (opts.output === "json") {
|
|
937
|
-
|
|
1030
|
+
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 }));
|
|
1031
|
+
const doc = buildWarnings.length ? { ...last, build_warnings: buildWarnings } : last;
|
|
1032
|
+
process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
|
|
938
1033
|
} else if (opts.output === "junit") {
|
|
939
1034
|
process.stdout.write(formatMultiSuiteJunit(last) + "\n");
|
|
940
1035
|
} else {
|
|
@@ -942,25 +1037,25 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
942
1037
|
Aggregate: ${last.status}
|
|
943
1038
|
`);
|
|
944
1039
|
for (const sr of last.suite_runs) {
|
|
945
|
-
const mark = sr.status === "passed" ?
|
|
946
|
-
const link = sr.url ?
|
|
1040
|
+
const mark = sr.status === "passed" ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
1041
|
+
const link = sr.url ? chalk5.dim(` ${sr.url}`) : "";
|
|
947
1042
|
process.stdout.write(
|
|
948
1043
|
` ${mark} ${sr.suite_name}: ${sr.passed} passed, ${sr.failed} failed${link}
|
|
949
1044
|
`
|
|
950
1045
|
);
|
|
951
1046
|
if (sr.error_message) {
|
|
952
|
-
process.stdout.write(` ${
|
|
1047
|
+
process.stdout.write(` ${chalk5.red(sr.error_message)}
|
|
953
1048
|
`);
|
|
954
1049
|
}
|
|
955
1050
|
for (const cr of sr.case_runs ?? []) {
|
|
956
|
-
const caseMark = cr.status === "passed" ?
|
|
957
|
-
const dur = cr.duration_ms ?
|
|
1051
|
+
const caseMark = cr.status === "passed" ? chalk5.green("\u2713") : cr.status === "cancelled" || cr.status === "pending" || cr.status === "running" ? chalk5.yellow("-") : chalk5.red("\u2717");
|
|
1052
|
+
const dur = cr.duration_ms ? chalk5.dim(` (${(cr.duration_ms / 1e3).toFixed(1)}s)`) : "";
|
|
958
1053
|
process.stdout.write(
|
|
959
|
-
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${
|
|
1054
|
+
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${chalk5.dim(cr.url)}
|
|
960
1055
|
`
|
|
961
1056
|
);
|
|
962
1057
|
if (cr.error_message && cr.status !== "passed") {
|
|
963
|
-
process.stdout.write(` ${
|
|
1058
|
+
process.stdout.write(` ${chalk5.red(cr.error_message)}
|
|
964
1059
|
`);
|
|
965
1060
|
}
|
|
966
1061
|
}
|
|
@@ -972,7 +1067,7 @@ Aggregate: ${last.status}
|
|
|
972
1067
|
}
|
|
973
1068
|
|
|
974
1069
|
// src/commands/run-test.ts
|
|
975
|
-
import
|
|
1070
|
+
import chalk6 from "chalk";
|
|
976
1071
|
async function runTest(testId, options) {
|
|
977
1072
|
const { token, baseUrl } = resolveAuth(options);
|
|
978
1073
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1046,13 +1141,14 @@ ${signal} received.
|
|
|
1046
1141
|
const target = describeRunTarget(response, options);
|
|
1047
1142
|
spinner.stop();
|
|
1048
1143
|
process.stderr.write(
|
|
1049
|
-
`${
|
|
1050
|
-
${
|
|
1051
|
-
${
|
|
1052
|
-
` + (target ? `${
|
|
1053
|
-
` : "") + `${
|
|
1144
|
+
`${chalk6.dim("Session:")} ${sessionId || "unknown"}
|
|
1145
|
+
${chalk6.dim("Device:")} ${response.device_name || "allocating..."}
|
|
1146
|
+
${chalk6.dim("Test:")} ${testId}
|
|
1147
|
+
` + (target ? `${chalk6.dim("App:")} ${target}
|
|
1148
|
+
` : "") + `${chalk6.dim("Run:")} ${response.run_id}
|
|
1054
1149
|
`
|
|
1055
1150
|
);
|
|
1151
|
+
printBuildWarning(response.build_warning);
|
|
1056
1152
|
spinner.start("Waiting for device...");
|
|
1057
1153
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
1058
1154
|
const result = await pollUntilComplete(
|
|
@@ -1070,7 +1166,10 @@ ${chalk5.dim("Test:")} ${testId}
|
|
|
1070
1166
|
process.stdin.setRawMode(false);
|
|
1071
1167
|
process.stdin.pause();
|
|
1072
1168
|
}
|
|
1073
|
-
outputResult(
|
|
1169
|
+
outputResult(
|
|
1170
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
1171
|
+
options
|
|
1172
|
+
);
|
|
1074
1173
|
process.exit(mapExitCode(result.status));
|
|
1075
1174
|
} catch (err) {
|
|
1076
1175
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -1096,7 +1195,7 @@ ${chalk5.dim("Test:")} ${testId}
|
|
|
1096
1195
|
}
|
|
1097
1196
|
|
|
1098
1197
|
// src/commands/list-suites.ts
|
|
1099
|
-
import
|
|
1198
|
+
import chalk7 from "chalk";
|
|
1100
1199
|
async function listSuites(options) {
|
|
1101
1200
|
const { token, baseUrl } = resolveAuth(options);
|
|
1102
1201
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1118,21 +1217,21 @@ async function listSuites(options) {
|
|
|
1118
1217
|
console.log("No test suites found.");
|
|
1119
1218
|
return;
|
|
1120
1219
|
}
|
|
1121
|
-
console.log(
|
|
1220
|
+
console.log(chalk7.bold(`
|
|
1122
1221
|
${suites.length} suite(s)
|
|
1123
1222
|
`));
|
|
1124
1223
|
for (const s of suites) {
|
|
1125
|
-
const platform = s.platform ?
|
|
1126
|
-
const tagsText = s.tags && s.tags.length > 0 ?
|
|
1224
|
+
const platform = s.platform ? chalk7.dim(` [${s.platform}]`) : "";
|
|
1225
|
+
const tagsText = s.tags && s.tags.length > 0 ? chalk7.dim(` [${s.tags.map((t) => t.display_name).join(", ")}]`) : "";
|
|
1127
1226
|
console.log(
|
|
1128
|
-
` ${
|
|
1227
|
+
` ${chalk7.cyan(s.id)} ${s.name}${platform}${tagsText} (${s.case_count} ${s.case_count === 1 ? "case" : "cases"})`
|
|
1129
1228
|
);
|
|
1130
1229
|
}
|
|
1131
1230
|
console.log();
|
|
1132
1231
|
}
|
|
1133
1232
|
|
|
1134
1233
|
// src/commands/list-apps.ts
|
|
1135
|
-
import
|
|
1234
|
+
import chalk8 from "chalk";
|
|
1136
1235
|
async function listApps(options) {
|
|
1137
1236
|
const { token, baseUrl } = resolveAuth(options);
|
|
1138
1237
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1151,20 +1250,20 @@ async function listApps(options) {
|
|
|
1151
1250
|
console.log("No apps found.");
|
|
1152
1251
|
return;
|
|
1153
1252
|
}
|
|
1154
|
-
console.log(
|
|
1253
|
+
console.log(chalk8.bold(`
|
|
1155
1254
|
${apps.length} app(s)
|
|
1156
1255
|
`));
|
|
1157
1256
|
for (const app of apps) {
|
|
1158
|
-
const platform =
|
|
1159
|
-
const pkg2 =
|
|
1160
|
-
console.log(` ${
|
|
1161
|
-
console.log(
|
|
1257
|
+
const platform = chalk8.dim(` [${app.platform}]`);
|
|
1258
|
+
const pkg2 = chalk8.dim(app.package_name);
|
|
1259
|
+
console.log(` ${chalk8.cyan(app.public_id)} ${app.name}${platform} ${pkg2}`);
|
|
1260
|
+
console.log(chalk8.dim(` ${baseUrl}/apps/${app.public_id}`));
|
|
1162
1261
|
}
|
|
1163
1262
|
console.log();
|
|
1164
1263
|
}
|
|
1165
1264
|
|
|
1166
1265
|
// src/commands/stop.ts
|
|
1167
|
-
import
|
|
1266
|
+
import chalk9 from "chalk";
|
|
1168
1267
|
async function stopCommand(sessionId, opts, deps) {
|
|
1169
1268
|
const id = sessionId?.trim();
|
|
1170
1269
|
if (!id) throw new UsageError("a session id is required: vibeview stop <session-id>");
|
|
@@ -1175,7 +1274,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1175
1274
|
} catch (err) {
|
|
1176
1275
|
const message = err instanceof Error ? err.message : String(err);
|
|
1177
1276
|
if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
|
|
1178
|
-
console.error(
|
|
1277
|
+
console.error(chalk9.red(`error: ${message}`));
|
|
1179
1278
|
process.exitCode = errorExitCode(err);
|
|
1180
1279
|
return;
|
|
1181
1280
|
}
|
|
@@ -1208,7 +1307,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1208
1307
|
}
|
|
1209
1308
|
|
|
1210
1309
|
// src/commands/create-test.ts
|
|
1211
|
-
import
|
|
1310
|
+
import chalk10 from "chalk";
|
|
1212
1311
|
import { existsSync, readFileSync } from "fs";
|
|
1213
1312
|
async function readStdin() {
|
|
1214
1313
|
return new Promise((resolve, reject) => {
|
|
@@ -1249,6 +1348,18 @@ async function resolveSteps(options) {
|
|
|
1249
1348
|
}
|
|
1250
1349
|
throw new Error("No steps provided. Use --steps, --steps-file, or pipe JSON to stdin.");
|
|
1251
1350
|
}
|
|
1351
|
+
function assertHasSteps(steps) {
|
|
1352
|
+
if (!Array.isArray(steps)) {
|
|
1353
|
+
throw new Error(
|
|
1354
|
+
`Steps must be a JSON array of instructions, e.g. '["Open the app", "Verify the Home screen shows"]'.`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
if (steps.length === 0) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
`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"]'.`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1252
1363
|
async function resolveAppId(client, appRef, platform) {
|
|
1253
1364
|
if (appRef.includes(".")) {
|
|
1254
1365
|
if (!platform) {
|
|
@@ -1270,6 +1381,7 @@ async function createTest(options) {
|
|
|
1270
1381
|
const client = new ApiClient(baseUrl, token);
|
|
1271
1382
|
try {
|
|
1272
1383
|
const steps = await resolveSteps(options);
|
|
1384
|
+
assertHasSteps(steps);
|
|
1273
1385
|
let appPublicId;
|
|
1274
1386
|
if (options.app) {
|
|
1275
1387
|
appPublicId = await resolveAppId(client, options.app, options.platform);
|
|
@@ -1303,20 +1415,20 @@ async function createTest(options) {
|
|
|
1303
1415
|
);
|
|
1304
1416
|
} else {
|
|
1305
1417
|
console.log(`
|
|
1306
|
-
${
|
|
1418
|
+
${chalk10.cyan(result.public_id)} ${result.name}`);
|
|
1307
1419
|
if (result.app_name) {
|
|
1308
|
-
console.log(` ${
|
|
1420
|
+
console.log(` ${chalk10.dim(`App: ${result.app_name}`)}`);
|
|
1309
1421
|
}
|
|
1310
1422
|
if (result.platform) {
|
|
1311
|
-
console.log(` ${
|
|
1423
|
+
console.log(` ${chalk10.dim(`Platform: ${result.platform}`)}`);
|
|
1312
1424
|
}
|
|
1313
|
-
console.log(` ${
|
|
1314
|
-
console.log(
|
|
1425
|
+
console.log(` ${chalk10.dim(`Steps: ${steps.length}`)}`);
|
|
1426
|
+
console.log(chalk10.dim(` ${baseUrl}/tests?tab=standalone
|
|
1315
1427
|
`));
|
|
1316
1428
|
}
|
|
1317
1429
|
} catch (err) {
|
|
1318
1430
|
if (options.output === "json") printJsonError(err);
|
|
1319
|
-
console.error(
|
|
1431
|
+
console.error(chalk10.red(err instanceof Error ? err.message : "Failed to create test"));
|
|
1320
1432
|
process.exit(
|
|
1321
1433
|
err instanceof ApiStatusError && err.status === 409 ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR
|
|
1322
1434
|
);
|
|
@@ -1400,7 +1512,7 @@ async function logout() {
|
|
|
1400
1512
|
}
|
|
1401
1513
|
|
|
1402
1514
|
// src/commands/dev-status.ts
|
|
1403
|
-
import
|
|
1515
|
+
import chalk11 from "chalk";
|
|
1404
1516
|
function buildLine(info) {
|
|
1405
1517
|
if (!info || typeof info.app_build_id !== "number") return null;
|
|
1406
1518
|
const version = info.app_version ? ` version ${info.app_version}${info.app_build_number ? ` (${info.app_build_number})` : ""}` : "";
|
|
@@ -1456,7 +1568,7 @@ async function devStatus(opts) {
|
|
|
1456
1568
|
console.error(` url: ${state.page_url}`);
|
|
1457
1569
|
console.error(` started: ${state.started_at}`);
|
|
1458
1570
|
}
|
|
1459
|
-
if (metro) console.error(
|
|
1571
|
+
if (metro) console.error(chalk11.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
|
|
1460
1572
|
process.exit(0);
|
|
1461
1573
|
}
|
|
1462
1574
|
|
|
@@ -1492,12 +1604,12 @@ async function devStop(opts) {
|
|
|
1492
1604
|
}
|
|
1493
1605
|
|
|
1494
1606
|
// src/commands/dev-reload.ts
|
|
1495
|
-
import
|
|
1607
|
+
import chalk12 from "chalk";
|
|
1496
1608
|
function fail(opts, code, message, exit) {
|
|
1497
1609
|
if (opts.json) {
|
|
1498
1610
|
console.log(JSON.stringify({ reloaded: false, code, message }));
|
|
1499
1611
|
} else {
|
|
1500
|
-
console.error(
|
|
1612
|
+
console.error(chalk12.red(`error: ${message}`));
|
|
1501
1613
|
}
|
|
1502
1614
|
process.exit(exit);
|
|
1503
1615
|
}
|
|
@@ -1550,7 +1662,7 @@ async function devReload(opts) {
|
|
|
1550
1662
|
api,
|
|
1551
1663
|
// Build output and the "reloaded in" line are human output: stderr, so
|
|
1552
1664
|
// stdout stays reserved for the --json result.
|
|
1553
|
-
log: (line) => console.error(
|
|
1665
|
+
log: (line) => console.error(chalk12.dim(line))
|
|
1554
1666
|
});
|
|
1555
1667
|
const started = Date.now();
|
|
1556
1668
|
try {
|
|
@@ -1571,10 +1683,10 @@ async function devReload(opts) {
|
|
|
1571
1683
|
}
|
|
1572
1684
|
|
|
1573
1685
|
// src/commands/builds.ts
|
|
1574
|
-
import
|
|
1686
|
+
import chalk14 from "chalk";
|
|
1575
1687
|
|
|
1576
1688
|
// src/lib/formatters/builds.ts
|
|
1577
|
-
import
|
|
1689
|
+
import chalk13 from "chalk";
|
|
1578
1690
|
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1579
1691
|
var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
1580
1692
|
function formatCostCell(costMicro, status) {
|
|
@@ -1582,13 +1694,13 @@ function formatCostCell(costMicro, status) {
|
|
|
1582
1694
|
return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
|
|
1583
1695
|
}
|
|
1584
1696
|
var STATUS_COLOR = {
|
|
1585
|
-
succeeded:
|
|
1586
|
-
failed:
|
|
1587
|
-
timed_out:
|
|
1588
|
-
infra_failed:
|
|
1589
|
-
canceled:
|
|
1590
|
-
running:
|
|
1591
|
-
queued:
|
|
1697
|
+
succeeded: chalk13.green,
|
|
1698
|
+
failed: chalk13.red,
|
|
1699
|
+
timed_out: chalk13.red,
|
|
1700
|
+
infra_failed: chalk13.red,
|
|
1701
|
+
canceled: chalk13.dim,
|
|
1702
|
+
running: chalk13.yellow,
|
|
1703
|
+
queued: chalk13.dim
|
|
1592
1704
|
};
|
|
1593
1705
|
function formatBuildsHuman(builds2) {
|
|
1594
1706
|
if (builds2.length === 0) {
|
|
@@ -1605,7 +1717,7 @@ function formatBuildsHuman(builds2) {
|
|
|
1605
1717
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1606
1718
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1607
1719
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
1608
|
-
const lines = [
|
|
1720
|
+
const lines = [chalk13.bold(join4(padCells(HEADERS)))];
|
|
1609
1721
|
for (const row of rows) {
|
|
1610
1722
|
const padded = padCells(row);
|
|
1611
1723
|
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
@@ -1664,7 +1776,7 @@ async function buildsLogs(id, opts, deps) {
|
|
|
1664
1776
|
console.log(JSON.stringify(logs));
|
|
1665
1777
|
} else {
|
|
1666
1778
|
if (logs.content) process.stdout.write(logs.content);
|
|
1667
|
-
console.error(
|
|
1779
|
+
console.error(chalk14.dim(`status: ${logs.status}`));
|
|
1668
1780
|
}
|
|
1669
1781
|
process.exitCode = 0;
|
|
1670
1782
|
return;
|
|
@@ -1761,7 +1873,7 @@ async function buildsDownload(id, opts, deps) {
|
|
|
1761
1873
|
if (opts.json) {
|
|
1762
1874
|
console.log(JSON.stringify(result));
|
|
1763
1875
|
} else {
|
|
1764
|
-
console.log(
|
|
1876
|
+
console.log(chalk14.green(`\u2713 saved ${result.path}`));
|
|
1765
1877
|
}
|
|
1766
1878
|
process.exitCode = 0;
|
|
1767
1879
|
} catch (err) {
|
|
@@ -1778,7 +1890,7 @@ async function buildsCancel(id, opts, deps) {
|
|
|
1778
1890
|
if (opts.json) {
|
|
1779
1891
|
console.log(JSON.stringify(build));
|
|
1780
1892
|
} else {
|
|
1781
|
-
console.log(`${
|
|
1893
|
+
console.log(`${chalk14.cyan(build.id)} ${build.status}`);
|
|
1782
1894
|
}
|
|
1783
1895
|
process.exitCode = 0;
|
|
1784
1896
|
} catch (err) {
|
|
@@ -1795,7 +1907,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1795
1907
|
if (opts.json) {
|
|
1796
1908
|
console.log(JSON.stringify(result));
|
|
1797
1909
|
} else {
|
|
1798
|
-
console.log(
|
|
1910
|
+
console.log(chalk14.green(`\u2713 deleted artifact for ${id}`));
|
|
1799
1911
|
}
|
|
1800
1912
|
process.exitCode = 0;
|
|
1801
1913
|
} catch (err) {
|
|
@@ -1806,11 +1918,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1806
1918
|
function printError(opts, err) {
|
|
1807
1919
|
const message = err instanceof Error ? err.message : String(err);
|
|
1808
1920
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1809
|
-
console.error(
|
|
1921
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1810
1922
|
}
|
|
1811
1923
|
|
|
1812
1924
|
// src/commands/submit.ts
|
|
1813
|
-
import
|
|
1925
|
+
import chalk15 from "chalk";
|
|
1814
1926
|
function defaultDeps2(client) {
|
|
1815
1927
|
return {
|
|
1816
1928
|
createSubmission: client.createSubmission.bind(client),
|
|
@@ -1834,7 +1946,7 @@ function submitConfigFor(cwd, platform) {
|
|
|
1834
1946
|
cfg = loadDevConfig(cwd);
|
|
1835
1947
|
} catch (err) {
|
|
1836
1948
|
console.error(
|
|
1837
|
-
|
|
1949
|
+
chalk15.yellow(
|
|
1838
1950
|
`warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
|
|
1839
1951
|
)
|
|
1840
1952
|
);
|
|
@@ -1982,12 +2094,12 @@ async function submitCommand(opts, deps) {
|
|
|
1982
2094
|
return;
|
|
1983
2095
|
}
|
|
1984
2096
|
if (!opts.json) {
|
|
1985
|
-
console.error(` ${
|
|
2097
|
+
console.error(` ${chalk15.cyan(sub.id)} ${chalk15.dim(`[${sub.platform}]`)}`);
|
|
1986
2098
|
}
|
|
1987
2099
|
const final = await followSubmission(sub, opts, d);
|
|
1988
2100
|
if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
|
|
1989
2101
|
console.error(
|
|
1990
|
-
|
|
2102
|
+
chalk15.dim("release status: draft \u2014 activate it from the Play Console when ready")
|
|
1991
2103
|
);
|
|
1992
2104
|
}
|
|
1993
2105
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
@@ -2018,11 +2130,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
|
2018
2130
|
});
|
|
2019
2131
|
emit(opts.json, { event: "preflight", ...result });
|
|
2020
2132
|
if (!opts.json) {
|
|
2021
|
-
const mark = result.ready ?
|
|
2133
|
+
const mark = result.ready ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
2022
2134
|
console.error(`${mark} ${result.message}`);
|
|
2023
2135
|
if (result.ready) {
|
|
2024
2136
|
console.error(
|
|
2025
|
-
|
|
2137
|
+
chalk15.dim(
|
|
2026
2138
|
` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
|
|
2027
2139
|
)
|
|
2028
2140
|
);
|
|
@@ -2056,7 +2168,7 @@ async function linkedIdentifier(platform, opts, deps) {
|
|
|
2056
2168
|
}
|
|
2057
2169
|
if (!app.package_name) return void 0;
|
|
2058
2170
|
if (!opts.json) {
|
|
2059
|
-
console.error(
|
|
2171
|
+
console.error(chalk15.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
|
|
2060
2172
|
}
|
|
2061
2173
|
return app.package_name;
|
|
2062
2174
|
}
|
|
@@ -2097,14 +2209,14 @@ async function followSubmission(created, opts, deps) {
|
|
|
2097
2209
|
if (opts.json) {
|
|
2098
2210
|
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
2099
2211
|
} else {
|
|
2100
|
-
console.error(
|
|
2212
|
+
console.error(chalk15.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
2101
2213
|
}
|
|
2102
2214
|
} else if (log.status === "running" && !printedRunning) {
|
|
2103
2215
|
printedRunning = true;
|
|
2104
2216
|
if (opts.json) {
|
|
2105
2217
|
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
2106
2218
|
} else {
|
|
2107
|
-
console.error(`${
|
|
2219
|
+
console.error(`${chalk15.cyan("\u25B6")} submitting`);
|
|
2108
2220
|
}
|
|
2109
2221
|
}
|
|
2110
2222
|
if (log.content) {
|
|
@@ -2146,7 +2258,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2146
2258
|
});
|
|
2147
2259
|
if (!opts.json) {
|
|
2148
2260
|
const ok = final.status === "succeeded";
|
|
2149
|
-
const mark = ok ?
|
|
2261
|
+
const mark = ok ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
2150
2262
|
const duration = formatDuration(final.started_at, final.finished_at);
|
|
2151
2263
|
let line = `${mark} ${final.status}`;
|
|
2152
2264
|
if (duration) line += ` (${duration})`;
|
|
@@ -2158,11 +2270,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2158
2270
|
function printError2(opts, err) {
|
|
2159
2271
|
const message = err instanceof Error ? err.message : String(err);
|
|
2160
2272
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2161
|
-
console.error(
|
|
2273
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2162
2274
|
}
|
|
2163
2275
|
|
|
2164
2276
|
// src/commands/versions.ts
|
|
2165
|
-
import
|
|
2277
|
+
import chalk16 from "chalk";
|
|
2166
2278
|
function defaultDeps3(client) {
|
|
2167
2279
|
return {
|
|
2168
2280
|
listAppVersions: client.listAppVersions.bind(client),
|
|
@@ -2212,7 +2324,7 @@ async function versionsSet(buildNumber, opts, deps) {
|
|
|
2212
2324
|
console.log(JSON.stringify(version));
|
|
2213
2325
|
} else {
|
|
2214
2326
|
console.log(
|
|
2215
|
-
|
|
2327
|
+
chalk16.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk16.dim(
|
|
2216
2328
|
" \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
|
|
2217
2329
|
)
|
|
2218
2330
|
);
|
|
@@ -2232,18 +2344,18 @@ function formatVersionsHuman(versions2) {
|
|
|
2232
2344
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2233
2345
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2234
2346
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2235
|
-
const lines = [
|
|
2347
|
+
const lines = [chalk16.bold(join4(padCells(HEADERS2)))];
|
|
2236
2348
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2237
2349
|
return lines.join("\n") + "\n";
|
|
2238
2350
|
}
|
|
2239
2351
|
function printError3(opts, err) {
|
|
2240
2352
|
const message = err instanceof Error ? err.message : String(err);
|
|
2241
2353
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2242
|
-
console.error(
|
|
2354
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2243
2355
|
}
|
|
2244
2356
|
|
|
2245
2357
|
// src/commands/whoami.ts
|
|
2246
|
-
import
|
|
2358
|
+
import chalk17 from "chalk";
|
|
2247
2359
|
function defaultDeps4(opts) {
|
|
2248
2360
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2249
2361
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -2255,6 +2367,15 @@ function effectiveRole(role, tokenRole) {
|
|
|
2255
2367
|
if (!tokenRole) return role;
|
|
2256
2368
|
return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
|
|
2257
2369
|
}
|
|
2370
|
+
function describeExpiry(expiresAt, now = /* @__PURE__ */ new Date()) {
|
|
2371
|
+
if (!expiresAt) return "never";
|
|
2372
|
+
const when = new Date(expiresAt);
|
|
2373
|
+
if (Number.isNaN(when.getTime())) return expiresAt;
|
|
2374
|
+
const date = when.toISOString().slice(0, 10);
|
|
2375
|
+
const days = Math.ceil((when.getTime() - now.getTime()) / 864e5);
|
|
2376
|
+
if (days <= 0) return `${date} (expired)`;
|
|
2377
|
+
return `${date} (in ${days} day${days === 1 ? "" : "s"})`;
|
|
2378
|
+
}
|
|
2258
2379
|
async function whoAmI(opts, deps) {
|
|
2259
2380
|
const d = deps ?? defaultDeps4(opts);
|
|
2260
2381
|
try {
|
|
@@ -2265,27 +2386,33 @@ async function whoAmI(opts, deps) {
|
|
|
2265
2386
|
process.exitCode = 0;
|
|
2266
2387
|
return;
|
|
2267
2388
|
}
|
|
2268
|
-
console.log(`${
|
|
2389
|
+
console.log(`${chalk17.bold("Logged in as")} ${me.email}`);
|
|
2269
2390
|
if (me.memberships.length === 1) {
|
|
2270
|
-
console.log(`${
|
|
2391
|
+
console.log(`${chalk17.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
2271
2392
|
} else if (me.memberships.length > 1) {
|
|
2272
|
-
console.log(
|
|
2393
|
+
console.log(chalk17.bold("Organizations"));
|
|
2273
2394
|
for (const m of me.memberships) {
|
|
2274
2395
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
2275
2396
|
}
|
|
2276
2397
|
}
|
|
2277
|
-
console.log(`${
|
|
2278
|
-
console.log(`${
|
|
2279
|
-
console.log(`${
|
|
2398
|
+
console.log(`${chalk17.bold("Member role")} ${me.role ?? "\u2014"}`);
|
|
2399
|
+
console.log(`${chalk17.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
|
|
2400
|
+
console.log(`${chalk17.bold("Effective role")} ${effective ?? "\u2014"}`);
|
|
2401
|
+
if (me.token_access) {
|
|
2402
|
+
console.log(
|
|
2403
|
+
`${chalk17.bold("Access")} ${me.token_access === "ci" ? "CI only (upload builds, run tests, read results)" : "Full access"}`
|
|
2404
|
+
);
|
|
2405
|
+
console.log(`${chalk17.bold("Expires")} ${describeExpiry(me.token_expires_at)}`);
|
|
2406
|
+
}
|
|
2280
2407
|
if (me.is_superadmin) {
|
|
2281
|
-
console.log(
|
|
2408
|
+
console.log(chalk17.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
2282
2409
|
}
|
|
2283
|
-
if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
|
|
2410
|
+
if (me.role === "admin" && me.token_role && me.token_role !== "admin" && me.token_access !== "ci") {
|
|
2284
2411
|
console.log(
|
|
2285
|
-
|
|
2412
|
+
chalk17.yellow(
|
|
2286
2413
|
`
|
|
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 ${
|
|
2414
|
+
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.
|
|
2415
|
+
Create an admin API token in the dashboard and use ${chalk17.bold("--token")} or ${chalk17.bold("VIBEVIEW_API_TOKEN")}.`
|
|
2289
2416
|
)
|
|
2290
2417
|
);
|
|
2291
2418
|
}
|
|
@@ -2295,7 +2422,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2295
2422
|
if (opts.json) {
|
|
2296
2423
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2297
2424
|
}
|
|
2298
|
-
console.error(
|
|
2425
|
+
console.error(chalk17.red(`error: ${message}`));
|
|
2299
2426
|
process.exitCode = errorExitCode(err);
|
|
2300
2427
|
}
|
|
2301
2428
|
}
|
|
@@ -2303,7 +2430,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2303
2430
|
// src/commands/env.ts
|
|
2304
2431
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
2305
2432
|
import { basename } from "path";
|
|
2306
|
-
import
|
|
2433
|
+
import chalk18 from "chalk";
|
|
2307
2434
|
import { password } from "@inquirer/prompts";
|
|
2308
2435
|
|
|
2309
2436
|
// src/lib/gitignore.ts
|
|
@@ -2359,7 +2486,7 @@ function defaultDeps5(client) {
|
|
|
2359
2486
|
isIgnored: (path) => isGitIgnored(path),
|
|
2360
2487
|
writeFile: writeSecureFile,
|
|
2361
2488
|
readFile: (path) => readFileAsync(path),
|
|
2362
|
-
warn: (message) => console.error(
|
|
2489
|
+
warn: (message) => console.error(chalk18.yellow(`warning: ${message}`))
|
|
2363
2490
|
};
|
|
2364
2491
|
}
|
|
2365
2492
|
async function envList(opts, deps) {
|
|
@@ -2391,7 +2518,7 @@ function formatEnvVarsHuman(variables) {
|
|
|
2391
2518
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2392
2519
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2393
2520
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2394
|
-
const lines = [
|
|
2521
|
+
const lines = [chalk18.bold(join4(padCells(HEADERS3)))];
|
|
2395
2522
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2396
2523
|
return lines.join("\n") + "\n";
|
|
2397
2524
|
}
|
|
@@ -2441,7 +2568,7 @@ function printSetResult(opts, result) {
|
|
|
2441
2568
|
if (opts.json) {
|
|
2442
2569
|
console.log(JSON.stringify(result));
|
|
2443
2570
|
} else {
|
|
2444
|
-
console.log(
|
|
2571
|
+
console.log(chalk18.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
2445
2572
|
}
|
|
2446
2573
|
}
|
|
2447
2574
|
async function envRm(name, opts, deps) {
|
|
@@ -2451,7 +2578,7 @@ async function envRm(name, opts, deps) {
|
|
|
2451
2578
|
if (opts.json) {
|
|
2452
2579
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2453
2580
|
} else {
|
|
2454
|
-
console.log(
|
|
2581
|
+
console.log(chalk18.green(`\u2713 removed ${name}`));
|
|
2455
2582
|
}
|
|
2456
2583
|
process.exitCode = 0;
|
|
2457
2584
|
} catch (err) {
|
|
@@ -2483,7 +2610,7 @@ async function envPull(opts, deps) {
|
|
|
2483
2610
|
if (opts.json) {
|
|
2484
2611
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
2485
2612
|
} else {
|
|
2486
|
-
console.log(
|
|
2613
|
+
console.log(chalk18.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
2487
2614
|
}
|
|
2488
2615
|
process.exitCode = 0;
|
|
2489
2616
|
} catch (err) {
|
|
@@ -2522,7 +2649,7 @@ function escapeEnvValue(value) {
|
|
|
2522
2649
|
function printError4(opts, err) {
|
|
2523
2650
|
const message = err instanceof Error ? err.message : String(err);
|
|
2524
2651
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2525
|
-
console.error(
|
|
2652
|
+
console.error(chalk18.red(`error: ${message}`));
|
|
2526
2653
|
}
|
|
2527
2654
|
|
|
2528
2655
|
// src/commands/credentials.ts
|
|
@@ -2534,7 +2661,7 @@ import {
|
|
|
2534
2661
|
} from "fs/promises";
|
|
2535
2662
|
import { mkdirSync } from "fs";
|
|
2536
2663
|
import { basename as basename2, extname, join } from "path";
|
|
2537
|
-
import
|
|
2664
|
+
import chalk19 from "chalk";
|
|
2538
2665
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
2539
2666
|
function clientFor2(opts) {
|
|
2540
2667
|
const { token, baseUrl } = resolveAuth(opts);
|
|
@@ -2607,7 +2734,7 @@ function defaultDeps6(client) {
|
|
|
2607
2734
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
2608
2735
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
2609
2736
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
2610
|
-
warn: (message) => console.error(
|
|
2737
|
+
warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
|
|
2611
2738
|
};
|
|
2612
2739
|
}
|
|
2613
2740
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -2676,7 +2803,7 @@ async function credentialsAdd(opts, deps) {
|
|
|
2676
2803
|
if (opts.json) {
|
|
2677
2804
|
console.log(JSON.stringify(result));
|
|
2678
2805
|
} else {
|
|
2679
|
-
console.log(
|
|
2806
|
+
console.log(chalk19.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
2680
2807
|
}
|
|
2681
2808
|
process.exitCode = 0;
|
|
2682
2809
|
} catch (err) {
|
|
@@ -2734,20 +2861,20 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2734
2861
|
}
|
|
2735
2862
|
if (opts.json) {
|
|
2736
2863
|
} else {
|
|
2737
|
-
console.log(
|
|
2864
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (android)`));
|
|
2738
2865
|
console.log("");
|
|
2739
|
-
console.log(` Keystore saved to: ${
|
|
2740
|
-
console.log(` Key alias: ${
|
|
2741
|
-
console.log(` Store password: ${
|
|
2742
|
-
console.log(` Key password: ${
|
|
2866
|
+
console.log(` Keystore saved to: ${chalk19.bold(savedTo)}`);
|
|
2867
|
+
console.log(` Key alias: ${chalk19.bold(result.key_alias)}`);
|
|
2868
|
+
console.log(` Store password: ${chalk19.bold(result.store_password)}`);
|
|
2869
|
+
console.log(` Key password: ${chalk19.bold(result.key_password)}`);
|
|
2743
2870
|
console.log("");
|
|
2744
2871
|
console.log(
|
|
2745
|
-
|
|
2872
|
+
chalk19.yellow(
|
|
2746
2873
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
2747
2874
|
)
|
|
2748
2875
|
);
|
|
2749
2876
|
console.log(
|
|
2750
|
-
|
|
2877
|
+
chalk19.yellow(
|
|
2751
2878
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
2752
2879
|
)
|
|
2753
2880
|
);
|
|
@@ -2773,19 +2900,19 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
2773
2900
|
if (opts.json) {
|
|
2774
2901
|
console.log(JSON.stringify(result));
|
|
2775
2902
|
} else {
|
|
2776
|
-
console.log(
|
|
2903
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
2777
2904
|
console.log("");
|
|
2778
|
-
console.log(` Scope: ${
|
|
2905
|
+
console.log(` Scope: ${chalk19.bold(result.app_public_id ?? "org-wide")}`);
|
|
2779
2906
|
const expires = formatExpires(result);
|
|
2780
2907
|
if (expires) {
|
|
2781
|
-
console.log(` Expires: ${
|
|
2908
|
+
console.log(` Expires: ${chalk19.bold(expires)}`);
|
|
2782
2909
|
}
|
|
2783
2910
|
console.log("");
|
|
2784
2911
|
console.log(
|
|
2785
2912
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
2786
2913
|
);
|
|
2787
2914
|
console.log(
|
|
2788
|
-
` Run ${
|
|
2915
|
+
` Run ${chalk19.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
2789
2916
|
);
|
|
2790
2917
|
}
|
|
2791
2918
|
process.exitCode = 0;
|
|
@@ -2851,17 +2978,17 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
2851
2978
|
await d.chmod(path, 384);
|
|
2852
2979
|
}
|
|
2853
2980
|
if (!opts.json) {
|
|
2854
|
-
console.log(
|
|
2981
|
+
console.log(chalk19.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
2855
2982
|
console.log("");
|
|
2856
|
-
console.log(` Saved to: ${
|
|
2983
|
+
console.log(` Saved to: ${chalk19.bold(savedTo)}`);
|
|
2857
2984
|
if (profileSavedTo !== null) {
|
|
2858
|
-
console.log(` Profile: ${
|
|
2985
|
+
console.log(` Profile: ${chalk19.bold(profileSavedTo)}`);
|
|
2859
2986
|
}
|
|
2860
2987
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
2861
|
-
console.log(` ${key}: ${
|
|
2988
|
+
console.log(` ${key}: ${chalk19.bold(value)}`);
|
|
2862
2989
|
}
|
|
2863
2990
|
console.log("");
|
|
2864
|
-
console.log(
|
|
2991
|
+
console.log(chalk19.yellow(" This download is recorded in your organization's audit log."));
|
|
2865
2992
|
}
|
|
2866
2993
|
process.exitCode = 0;
|
|
2867
2994
|
} catch (err) {
|
|
@@ -2892,13 +3019,13 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2892
3019
|
if (opts.json) {
|
|
2893
3020
|
console.log(JSON.stringify(result));
|
|
2894
3021
|
} else {
|
|
2895
|
-
console.log(
|
|
3022
|
+
console.log(chalk19.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2896
3023
|
console.log("");
|
|
2897
|
-
console.log(` Key ID: ${
|
|
2898
|
-
console.log(` Issuer ID: ${
|
|
3024
|
+
console.log(` Key ID: ${chalk19.bold(result.key_id)}`);
|
|
3025
|
+
console.log(` Issuer ID: ${chalk19.bold(result.issuer_id)}`);
|
|
2899
3026
|
console.log("");
|
|
2900
3027
|
console.log(
|
|
2901
|
-
|
|
3028
|
+
chalk19.yellow(
|
|
2902
3029
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2903
3030
|
)
|
|
2904
3031
|
);
|
|
@@ -2933,7 +3060,7 @@ function formatAscKeysHuman(keys) {
|
|
|
2933
3060
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2934
3061
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2935
3062
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2936
|
-
const lines = [
|
|
3063
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
2937
3064
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2938
3065
|
return lines.join("\n") + "\n";
|
|
2939
3066
|
}
|
|
@@ -2944,7 +3071,7 @@ async function ascKeyRm(name, opts, deps) {
|
|
|
2944
3071
|
if (opts.json) {
|
|
2945
3072
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2946
3073
|
} else {
|
|
2947
|
-
console.log(
|
|
3074
|
+
console.log(chalk19.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2948
3075
|
}
|
|
2949
3076
|
process.exitCode = 0;
|
|
2950
3077
|
} catch (err) {
|
|
@@ -2969,12 +3096,12 @@ async function playKeyAdd(opts, deps) {
|
|
|
2969
3096
|
if (opts.json) {
|
|
2970
3097
|
console.log(JSON.stringify(result));
|
|
2971
3098
|
} else {
|
|
2972
|
-
console.log(
|
|
3099
|
+
console.log(chalk19.green(`\u2713 added Google Play key ${result.name}`));
|
|
2973
3100
|
console.log("");
|
|
2974
|
-
console.log(` Service account: ${
|
|
3101
|
+
console.log(` Service account: ${chalk19.bold(result.client_email)}`);
|
|
2975
3102
|
console.log("");
|
|
2976
3103
|
console.log(
|
|
2977
|
-
|
|
3104
|
+
chalk19.yellow(
|
|
2978
3105
|
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2979
3106
|
)
|
|
2980
3107
|
);
|
|
@@ -3009,7 +3136,7 @@ function formatPlayKeysHuman(keys) {
|
|
|
3009
3136
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3010
3137
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3011
3138
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3012
|
-
const lines = [
|
|
3139
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
3013
3140
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3014
3141
|
return lines.join("\n") + "\n";
|
|
3015
3142
|
}
|
|
@@ -3020,7 +3147,7 @@ async function playKeyRm(name, opts, deps) {
|
|
|
3020
3147
|
if (opts.json) {
|
|
3021
3148
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3022
3149
|
} else {
|
|
3023
|
-
console.log(
|
|
3150
|
+
console.log(chalk19.green(`\u2713 removed Google Play key ${name}`));
|
|
3024
3151
|
}
|
|
3025
3152
|
process.exitCode = 0;
|
|
3026
3153
|
} catch (err) {
|
|
@@ -3089,7 +3216,7 @@ function formatCredentialsHuman(credentials2) {
|
|
|
3089
3216
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3090
3217
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3091
3218
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3092
|
-
const lines = [
|
|
3219
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
3093
3220
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3094
3221
|
return lines.join("\n") + "\n";
|
|
3095
3222
|
}
|
|
@@ -3100,7 +3227,7 @@ async function credentialsRm(name, opts, deps) {
|
|
|
3100
3227
|
if (opts.json) {
|
|
3101
3228
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3102
3229
|
} else {
|
|
3103
|
-
console.log(
|
|
3230
|
+
console.log(chalk19.green(`\u2713 removed ${name}`));
|
|
3104
3231
|
}
|
|
3105
3232
|
process.exitCode = 0;
|
|
3106
3233
|
} catch (err) {
|
|
@@ -3112,16 +3239,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3112
3239
|
const message = err instanceof Error ? err.message : String(err);
|
|
3113
3240
|
if (opts.json) {
|
|
3114
3241
|
console.log(JSON.stringify({ event: "error", message }));
|
|
3115
|
-
console.error(
|
|
3242
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3116
3243
|
return;
|
|
3117
3244
|
}
|
|
3118
|
-
console.error(
|
|
3245
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3119
3246
|
console.error(
|
|
3120
|
-
|
|
3247
|
+
chalk19.red(
|
|
3121
3248
|
`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
3249
|
)
|
|
3123
3250
|
);
|
|
3124
|
-
console.error(
|
|
3251
|
+
console.error(chalk19.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
3125
3252
|
console.error("");
|
|
3126
3253
|
console.error(` Key alias: ${result.key_alias}`);
|
|
3127
3254
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -3135,11 +3262,11 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3135
3262
|
function printError5(opts, err) {
|
|
3136
3263
|
const message = err instanceof Error ? err.message : String(err);
|
|
3137
3264
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3138
|
-
console.error(
|
|
3265
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3139
3266
|
}
|
|
3140
3267
|
|
|
3141
3268
|
// src/commands/webhooks.ts
|
|
3142
|
-
import
|
|
3269
|
+
import chalk20 from "chalk";
|
|
3143
3270
|
function clientFor3(opts) {
|
|
3144
3271
|
const { token, baseUrl } = resolveAuth(opts);
|
|
3145
3272
|
return new ApiClient(baseUrl, token);
|
|
@@ -3170,14 +3297,14 @@ async function webhooksAdd(opts, deps) {
|
|
|
3170
3297
|
if (opts.json) {
|
|
3171
3298
|
console.log(JSON.stringify(result));
|
|
3172
3299
|
} else {
|
|
3173
|
-
console.log(
|
|
3300
|
+
console.log(chalk20.green(`\u2713 added ${result.id} ${result.url}`));
|
|
3174
3301
|
console.log("");
|
|
3175
|
-
console.log(` Signing secret: ${
|
|
3302
|
+
console.log(` Signing secret: ${chalk20.bold(result.secret)}`);
|
|
3176
3303
|
if (opts.secret !== void 0) {
|
|
3177
|
-
console.log(
|
|
3304
|
+
console.log(chalk20.yellow(" (This is the secret you provided.)"));
|
|
3178
3305
|
}
|
|
3179
3306
|
console.log(
|
|
3180
|
-
|
|
3307
|
+
chalk20.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
3181
3308
|
);
|
|
3182
3309
|
}
|
|
3183
3310
|
process.exitCode = 0;
|
|
@@ -3208,7 +3335,7 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
3208
3335
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3209
3336
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3210
3337
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3211
|
-
const lines = [
|
|
3338
|
+
const lines = [chalk20.bold(join4(padCells(HEADERS3)))];
|
|
3212
3339
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3213
3340
|
return lines.join("\n") + "\n";
|
|
3214
3341
|
}
|
|
@@ -3234,7 +3361,7 @@ async function webhooksRm(id, opts, deps) {
|
|
|
3234
3361
|
if (opts.json) {
|
|
3235
3362
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
3236
3363
|
} else {
|
|
3237
|
-
console.log(
|
|
3364
|
+
console.log(chalk20.green(`\u2713 removed ${id}`));
|
|
3238
3365
|
}
|
|
3239
3366
|
process.exitCode = 0;
|
|
3240
3367
|
} catch (err) {
|
|
@@ -3267,13 +3394,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3267
3394
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
3268
3395
|
if (ld.status === "succeeded") {
|
|
3269
3396
|
if (!opts.json) {
|
|
3270
|
-
console.log(
|
|
3397
|
+
console.log(chalk20.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
3271
3398
|
}
|
|
3272
3399
|
process.exitCode = 0;
|
|
3273
3400
|
} else {
|
|
3274
3401
|
if (!opts.json) {
|
|
3275
3402
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
3276
|
-
console.error(
|
|
3403
|
+
console.error(chalk20.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
|
|
3277
3404
|
}
|
|
3278
3405
|
process.exitCode = 1;
|
|
3279
3406
|
}
|
|
@@ -3282,7 +3409,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3282
3409
|
}
|
|
3283
3410
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
3284
3411
|
console.error(
|
|
3285
|
-
|
|
3412
|
+
chalk20.yellow(
|
|
3286
3413
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
3287
3414
|
)
|
|
3288
3415
|
);
|
|
@@ -3295,7 +3422,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3295
3422
|
function printError6(opts, err) {
|
|
3296
3423
|
const message = err instanceof Error ? err.message : String(err);
|
|
3297
3424
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3298
|
-
console.error(
|
|
3425
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3299
3426
|
}
|
|
3300
3427
|
|
|
3301
3428
|
// src/commands/list-devices.ts
|
|
@@ -3393,7 +3520,7 @@ import { dirname, join as join2 } from "path";
|
|
|
3393
3520
|
import { fileURLToPath } from "url";
|
|
3394
3521
|
import { spawnSync } from "child_process";
|
|
3395
3522
|
import { confirm } from "@inquirer/prompts";
|
|
3396
|
-
import
|
|
3523
|
+
import chalk21 from "chalk";
|
|
3397
3524
|
function resolveShippedSkillPath(fromDir) {
|
|
3398
3525
|
for (const rel of ["..", join2("..", "..")]) {
|
|
3399
3526
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -3421,23 +3548,23 @@ function registerMcp() {
|
|
|
3421
3548
|
encoding: "utf8"
|
|
3422
3549
|
});
|
|
3423
3550
|
if (res.status === 0) {
|
|
3424
|
-
console.log(
|
|
3551
|
+
console.log(chalk21.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
3425
3552
|
return;
|
|
3426
3553
|
}
|
|
3427
3554
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
3428
3555
|
if (out.includes("already exists")) {
|
|
3429
|
-
console.log(
|
|
3556
|
+
console.log(chalk21.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
3430
3557
|
return;
|
|
3431
3558
|
}
|
|
3432
|
-
console.error(
|
|
3433
|
-
console.error(
|
|
3559
|
+
console.error(chalk21.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
3560
|
+
console.error(chalk21.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
3434
3561
|
}
|
|
3435
3562
|
async function agentSetup(opts) {
|
|
3436
3563
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3437
3564
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
3438
3565
|
if (!shipped) {
|
|
3439
3566
|
console.error(
|
|
3440
|
-
|
|
3567
|
+
chalk21.red(
|
|
3441
3568
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
3442
3569
|
)
|
|
3443
3570
|
);
|
|
@@ -3446,9 +3573,9 @@ async function agentSetup(opts) {
|
|
|
3446
3573
|
}
|
|
3447
3574
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
3448
3575
|
const result = installSkill(shipped, targetDir);
|
|
3449
|
-
console.log(
|
|
3576
|
+
console.log(chalk21.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
3450
3577
|
console.log(
|
|
3451
|
-
|
|
3578
|
+
chalk21.dim(
|
|
3452
3579
|
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
3580
|
)
|
|
3454
3581
|
);
|
|
@@ -3456,7 +3583,7 @@ async function agentSetup(opts) {
|
|
|
3456
3583
|
if (!claudeCliAvailable()) {
|
|
3457
3584
|
if (opts.mcp === true) {
|
|
3458
3585
|
console.error(
|
|
3459
|
-
|
|
3586
|
+
chalk21.yellow(
|
|
3460
3587
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
3461
3588
|
)
|
|
3462
3589
|
);
|
|
@@ -3470,7 +3597,7 @@ async function agentSetup(opts) {
|
|
|
3470
3597
|
}
|
|
3471
3598
|
console.log(
|
|
3472
3599
|
`
|
|
3473
|
-
Suggested line for your project's ${
|
|
3600
|
+
Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bold(
|
|
3474
3601
|
"AGENTS.md"
|
|
3475
3602
|
)} for other agents):
|
|
3476
3603
|
`
|
|
@@ -3478,7 +3605,7 @@ Suggested line for your project's ${chalk20.bold("CLAUDE.md")} (or ${chalk20.bol
|
|
|
3478
3605
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
3479
3606
|
`);
|
|
3480
3607
|
console.log(
|
|
3481
|
-
|
|
3608
|
+
chalk21.dim(
|
|
3482
3609
|
"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
3610
|
)
|
|
3484
3611
|
);
|
|
@@ -3613,6 +3740,7 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
|
|
|
3613
3740
|
}
|
|
3614
3741
|
try {
|
|
3615
3742
|
parseRunPlatform(opts.platform);
|
|
3743
|
+
refusePlatformWithApp(opts);
|
|
3616
3744
|
} catch (err) {
|
|
3617
3745
|
process.stderr.write(`error: ${err.message}
|
|
3618
3746
|
`);
|
|
@@ -3681,6 +3809,7 @@ program.command("run-test <test-id>").description("Run a single test case").opti
|
|
|
3681
3809
|
}
|
|
3682
3810
|
try {
|
|
3683
3811
|
parseRunPlatform(opts.platform);
|
|
3812
|
+
refusePlatformWithApp(opts);
|
|
3684
3813
|
} catch (err) {
|
|
3685
3814
|
process.stderr.write(`error: ${err.message}
|
|
3686
3815
|
`);
|
|
@@ -3899,7 +4028,7 @@ program.command("dev-reload").description(
|
|
|
3899
4028
|
program.command("mcp").description(
|
|
3900
4029
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3901
4030
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3902
|
-
const { runMcpServer } = await import("./server-
|
|
4031
|
+
const { runMcpServer } = await import("./server-GPFOZOFL.js");
|
|
3903
4032
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
3904
4033
|
});
|
|
3905
4034
|
program.command("agent-setup").description(
|