vibeview 0.29.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/{chunk-T6EQQV6E.js → chunk-P6UIJALK.js} +256 -81
- package/dist/{chunk-PBMB6TG4.js → chunk-ZZK7YQSP.js} +33 -6
- package/dist/cli.js +402 -205
- package/dist/{server-IGY2VPYT.js → server-GPFOZOFL.js} +8 -4
- package/dist/{upload-app-FEZG23NU.js → upload-app-2LVG4LMB.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +10 -5
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
listBuilds,
|
|
16
16
|
loadDevConfig,
|
|
17
17
|
newTransitionState,
|
|
18
|
+
platformLabel,
|
|
18
19
|
platformWithArticle,
|
|
19
20
|
pressButtonsHelp,
|
|
20
21
|
printOutcomeLine,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
readDevState,
|
|
23
24
|
readMetroState,
|
|
24
25
|
runAgentVerb
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-P6UIJALK.js";
|
|
26
27
|
import {
|
|
27
28
|
ApiClient,
|
|
28
29
|
ApiStatusError,
|
|
@@ -43,14 +44,14 @@ import {
|
|
|
43
44
|
resolveAuth,
|
|
44
45
|
saveConfig,
|
|
45
46
|
uploadApp
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-ZZK7YQSP.js";
|
|
47
48
|
|
|
48
49
|
// src/cli.ts
|
|
49
50
|
import { createRequire } from "module";
|
|
50
51
|
import { Command, Option } from "commander";
|
|
51
52
|
|
|
52
53
|
// src/commands/run-suite.ts
|
|
53
|
-
import
|
|
54
|
+
import chalk4 from "chalk";
|
|
54
55
|
|
|
55
56
|
// src/lib/poll.ts
|
|
56
57
|
import chalk2 from "chalk";
|
|
@@ -144,6 +145,10 @@ function formatHuman(result, verbose) {
|
|
|
144
145
|
lines.push(`
|
|
145
146
|
${chalk.red("Error:")} ${result.error_message}`);
|
|
146
147
|
}
|
|
148
|
+
if (result.build_warning && result.status !== "passed") {
|
|
149
|
+
lines.push(`
|
|
150
|
+
${chalk.yellow("Note:")} ${result.build_warning}`);
|
|
151
|
+
}
|
|
147
152
|
lines.push("");
|
|
148
153
|
return lines.join("\n");
|
|
149
154
|
}
|
|
@@ -173,9 +178,16 @@ function formatJunit(result) {
|
|
|
173
178
|
lines.push(
|
|
174
179
|
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
175
180
|
);
|
|
176
|
-
if (result.url) {
|
|
181
|
+
if (result.url || result.build_warning) {
|
|
177
182
|
lines.push(" <properties>");
|
|
178
|
-
|
|
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
|
+
}
|
|
179
191
|
lines.push(" </properties>");
|
|
180
192
|
}
|
|
181
193
|
for (const tr of cases) {
|
|
@@ -224,46 +236,97 @@ function formatJunitError(name, message, url) {
|
|
|
224
236
|
return lines.join("\n");
|
|
225
237
|
}
|
|
226
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
|
+
}
|
|
227
257
|
function formatMultiSuiteJunit(result, opts = {}) {
|
|
228
|
-
const unfinished = (status) => opts.timedOut !== void 0 && !MULTI_TERMINAL.has(status);
|
|
229
|
-
const errored = (status) => status === "error" || unfinished(status);
|
|
230
|
-
const tests = result.suite_runs.reduce((sum, sr) => sum + sr.passed + sr.failed, 0);
|
|
231
|
-
const failures = result.suite_runs.reduce((sum, sr) => sum + sr.failed, 0);
|
|
232
|
-
const noSuites = opts.timedOut !== void 0 && result.suite_runs.length === 0;
|
|
233
|
-
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 ?? "");
|
|
234
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);
|
|
235
306
|
const lines = [];
|
|
236
307
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
237
308
|
lines.push(
|
|
238
|
-
`<testsuites name="vibeview-multi" tests="${tests}" failures="${failures}" errors="${
|
|
309
|
+
`<testsuites name="vibeview-multi" tests="${tests}" failures="${sum("failures")}" errors="${errors}" skipped="${sum("skipped")}">`
|
|
239
310
|
);
|
|
240
|
-
for (const
|
|
241
|
-
const suiteName = escapeXml(
|
|
242
|
-
const suiteTests = sr.passed + sr.failed;
|
|
243
|
-
const suiteFails = sr.failed;
|
|
244
|
-
const suiteErrors = errored(sr.status) ? 1 : 0;
|
|
311
|
+
for (const s of suites) {
|
|
312
|
+
const suiteName = escapeXml(s.suiteName);
|
|
245
313
|
lines.push(
|
|
246
|
-
` <testsuite name="${suiteName}" tests="${
|
|
314
|
+
` <testsuite name="${suiteName}" tests="${s.tests}" failures="${s.failures}" errors="${s.errors}" skipped="${s.skipped}" timestamp="${timestamp}">`
|
|
247
315
|
);
|
|
248
|
-
if (sr.url) {
|
|
316
|
+
if (s.sr.url) {
|
|
249
317
|
lines.push(" <properties>");
|
|
250
|
-
lines.push(` <property name="vibeview.run_url" value="${escapeXml(sr.url)}" />`);
|
|
318
|
+
lines.push(` <property name="vibeview.run_url" value="${escapeXml(s.sr.url)}" />`);
|
|
251
319
|
lines.push(" </properties>");
|
|
252
320
|
}
|
|
253
|
-
|
|
254
|
-
const msg = escapeXml(`${opts.timedOut} (suite was ${sr.status})`);
|
|
255
|
-
lines.push(` <error message="${msg}" type="InfraError" />`);
|
|
256
|
-
} else if (suiteErrors > 0) {
|
|
257
|
-
lines.push(` <error message="Suite errored" type="InfraError" />`);
|
|
258
|
-
}
|
|
321
|
+
for (const c of s.cases) lines.push(...caseLines(c, suiteName));
|
|
259
322
|
lines.push(" </testsuite>");
|
|
260
323
|
}
|
|
261
324
|
if (noSuites) {
|
|
262
|
-
const
|
|
325
|
+
const name = "vibeview run-suites";
|
|
263
326
|
lines.push(
|
|
264
|
-
` <testsuite name="
|
|
327
|
+
` <testsuite name="${name}" tests="1" failures="0" errors="1" skipped="0" timestamp="${timestamp}">`
|
|
265
328
|
);
|
|
266
|
-
lines.push(
|
|
329
|
+
lines.push(...caseLines({ name, time: "0", verdict: "error", message: opts.timedOut }, name));
|
|
267
330
|
lines.push(" </testsuite>");
|
|
268
331
|
}
|
|
269
332
|
lines.push("</testsuites>");
|
|
@@ -570,6 +633,42 @@ function mapExitCode(status) {
|
|
|
570
633
|
return EXIT_INFRA_ERROR;
|
|
571
634
|
}
|
|
572
635
|
|
|
636
|
+
// src/lib/run-target.ts
|
|
637
|
+
import chalk3 from "chalk";
|
|
638
|
+
var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
639
|
+
function parseRunPlatform(value) {
|
|
640
|
+
if (value === void 0) return void 0;
|
|
641
|
+
if (!RUN_PLATFORMS.includes(value)) {
|
|
642
|
+
throw new Error(`--platform must be one of ${RUN_PLATFORMS.join(", ")}, got "${value}"`);
|
|
643
|
+
}
|
|
644
|
+
return value;
|
|
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
|
+
}
|
|
658
|
+
function describeRunTarget(response, options) {
|
|
659
|
+
const asked = Boolean(options.platform || options.app);
|
|
660
|
+
if (!response.app_id) {
|
|
661
|
+
if (asked) {
|
|
662
|
+
throw new Error(
|
|
663
|
+
"this VibeView server does not support --platform/--app on test runs yet; the run was stopped rather than run on the test\u2019s own platform"
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
if (!asked) return null;
|
|
669
|
+
return `${response.app_id} (${platformLabel(response.platform ?? "")})`;
|
|
670
|
+
}
|
|
671
|
+
|
|
573
672
|
// src/commands/run-suite.ts
|
|
574
673
|
async function runSuite(suiteId, options) {
|
|
575
674
|
const { token, baseUrl } = resolveAuth(options);
|
|
@@ -635,17 +734,22 @@ ${signal} received.
|
|
|
635
734
|
deviceCategory: options.deviceCategory,
|
|
636
735
|
device: options.device,
|
|
637
736
|
build: options.build,
|
|
638
|
-
metadata: options.metadata
|
|
737
|
+
metadata: options.metadata,
|
|
738
|
+
platform: options.platform,
|
|
739
|
+
app: options.app
|
|
639
740
|
});
|
|
640
741
|
sessionId = response.session_id;
|
|
641
742
|
runId = response.run_id;
|
|
743
|
+
const target = describeRunTarget(response, options);
|
|
642
744
|
spinner.stop();
|
|
643
745
|
process.stderr.write(
|
|
644
|
-
`${
|
|
645
|
-
|
|
646
|
-
`
|
|
746
|
+
`${chalk4.dim("Suite:")} ${suiteId}
|
|
747
|
+
` + (target ? `${chalk4.dim("App:")} ${target}
|
|
748
|
+
` : "") + `${chalk4.dim("Run:")} ${response.run_id}
|
|
749
|
+
` + (sessionId ? `${chalk4.dim("Session:")} ${sessionId}
|
|
647
750
|
` : "")
|
|
648
751
|
);
|
|
752
|
+
printBuildWarning(response.build_warning);
|
|
649
753
|
spinner.start("Waiting for device...");
|
|
650
754
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
651
755
|
const result = await pollUntilComplete(
|
|
@@ -663,7 +767,10 @@ ${chalk3.dim("Run:")} ${response.run_id}
|
|
|
663
767
|
process.stdin.setRawMode(false);
|
|
664
768
|
process.stdin.pause();
|
|
665
769
|
}
|
|
666
|
-
outputResult(
|
|
770
|
+
outputResult(
|
|
771
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
772
|
+
options
|
|
773
|
+
);
|
|
667
774
|
process.exit(mapExitCode(result.status));
|
|
668
775
|
} catch (err) {
|
|
669
776
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -689,7 +796,7 @@ ${chalk3.dim("Run:")} ${response.run_id}
|
|
|
689
796
|
}
|
|
690
797
|
|
|
691
798
|
// src/commands/run-suites.ts
|
|
692
|
-
import
|
|
799
|
+
import chalk5 from "chalk";
|
|
693
800
|
var AGGREGATE_TIMEOUT_CAP_S = 7200;
|
|
694
801
|
async function runSuites(opts) {
|
|
695
802
|
if (!opts.tag || opts.tag.length === 0) {
|
|
@@ -764,13 +871,13 @@ ${signal} received.
|
|
|
764
871
|
const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
|
|
765
872
|
if (opts.output === "json") printJsonError(noMatch);
|
|
766
873
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", noMatch));
|
|
767
|
-
process.stderr.write(
|
|
874
|
+
process.stderr.write(chalk5.yellow(`${noMatch}
|
|
768
875
|
`));
|
|
769
876
|
process.exit(EXIT_INFRA_ERROR);
|
|
770
877
|
}
|
|
771
878
|
if (opts.output === "json") printJsonError(err);
|
|
772
879
|
if (opts.output === "junit") console.log(formatJunitError("vibeview run-suites", msg));
|
|
773
|
-
process.stderr.write(
|
|
880
|
+
process.stderr.write(chalk5.red(`Failed to start: ${msg}
|
|
774
881
|
`));
|
|
775
882
|
process.exit(EXIT_INFRA_ERROR);
|
|
776
883
|
}
|
|
@@ -779,16 +886,17 @@ ${signal} received.
|
|
|
779
886
|
const matchedNames = start.matched_suites?.map((s) => s.name) ?? [];
|
|
780
887
|
const totalMatched = start.matched_suite_ids.length;
|
|
781
888
|
process.stderr.write(
|
|
782
|
-
`${
|
|
783
|
-
${
|
|
784
|
-
${
|
|
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(", ") : ""}
|
|
785
892
|
`
|
|
786
893
|
);
|
|
894
|
+
for (const s of start.matched_suites ?? []) printBuildWarning(s.build_warning, s.name);
|
|
787
895
|
const excluded = start.excluded_suites ?? [];
|
|
788
896
|
if (excluded.length) {
|
|
789
897
|
const names = excluded.map((s) => s.name).join(", ");
|
|
790
898
|
process.stderr.write(
|
|
791
|
-
|
|
899
|
+
chalk5.dim(`Excluded ${excluded.length} suite(s) (platform mismatch): ${names}
|
|
792
900
|
`)
|
|
793
901
|
);
|
|
794
902
|
}
|
|
@@ -802,20 +910,28 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
802
910
|
const suiteStateBefore = /* @__PURE__ */ new Map();
|
|
803
911
|
const suiteStartedPrinted = /* @__PURE__ */ new Set();
|
|
804
912
|
let lastQueueReason = null;
|
|
913
|
+
let pending = [];
|
|
805
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;
|
|
806
920
|
spinner.stop();
|
|
807
|
-
process.stderr.write(line + "\n");
|
|
808
|
-
|
|
921
|
+
for (const line of pending) process.stderr.write(line + "\n");
|
|
922
|
+
pending = [];
|
|
923
|
+
if (nextText !== null) spinner.start(nextText);
|
|
809
924
|
};
|
|
810
925
|
const TERMINAL = /* @__PURE__ */ new Set(["passed", "failed", "error", "cancelled"]);
|
|
811
926
|
let last = null;
|
|
812
927
|
const abandon = async (reason) => {
|
|
928
|
+
flush(null);
|
|
813
929
|
spinner.stop();
|
|
814
930
|
if (process.stdin.isTTY) {
|
|
815
931
|
process.stdin.setRawMode(false);
|
|
816
932
|
process.stdin.pause();
|
|
817
933
|
}
|
|
818
|
-
process.stderr.write(
|
|
934
|
+
process.stderr.write(chalk5.red(`${reason}
|
|
819
935
|
`));
|
|
820
936
|
process.stderr.write("Cancelling multi-suite run...\n");
|
|
821
937
|
try {
|
|
@@ -823,7 +939,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
823
939
|
process.stderr.write("Cancellation requested.\n");
|
|
824
940
|
} catch (err) {
|
|
825
941
|
const m = err instanceof Error ? err.message : String(err);
|
|
826
|
-
process.stderr.write(
|
|
942
|
+
process.stderr.write(chalk5.yellow(`Could not cancel the run: ${m}
|
|
827
943
|
`));
|
|
828
944
|
}
|
|
829
945
|
if (opts.output === "json") {
|
|
@@ -851,6 +967,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
851
967
|
return abandon(`Could not read the run's status: ${m}`);
|
|
852
968
|
}
|
|
853
969
|
let doneCount = 0;
|
|
970
|
+
let nextText;
|
|
854
971
|
let queuedChild;
|
|
855
972
|
for (const sr of last.suite_runs) {
|
|
856
973
|
const cur = sr.status;
|
|
@@ -860,17 +977,17 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
860
977
|
}
|
|
861
978
|
if (cur === "running" && !suiteStartedPrinted.has(sr.run_id)) {
|
|
862
979
|
suiteStartedPrinted.add(sr.run_id);
|
|
863
|
-
print(`${
|
|
980
|
+
print(`${chalk5.cyan("\u25B6")} ${chalk5.bold(sr.suite_name)} started`);
|
|
864
981
|
}
|
|
865
982
|
const wasTerminal = prev !== void 0 && TERMINAL.has(prev);
|
|
866
983
|
if (TERMINAL.has(cur) && !wasTerminal) {
|
|
867
|
-
const mark = cur === "passed" ?
|
|
868
|
-
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) : "";
|
|
869
986
|
print(
|
|
870
|
-
`${mark} ${
|
|
987
|
+
`${mark} ${chalk5.bold(sr.suite_name)} ${cur}: ${sr.passed} passed, ${sr.failed} failed${link}`
|
|
871
988
|
);
|
|
872
989
|
if (sr.error_message) {
|
|
873
|
-
print(` ${
|
|
990
|
+
print(` ${chalk5.red(sr.error_message)}`);
|
|
874
991
|
}
|
|
875
992
|
}
|
|
876
993
|
suiteStateBefore.set(sr.run_id, cur);
|
|
@@ -883,16 +1000,20 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
883
1000
|
const why = queueReasonCopy(q.reason);
|
|
884
1001
|
if (why && why !== lastQueueReason) {
|
|
885
1002
|
print(
|
|
886
|
-
`${
|
|
1003
|
+
`${chalk5.yellow("\u23F3")} ${chalk5.bold(queuedChild.suite_name)} is waiting for a device: ${why}`
|
|
887
1004
|
);
|
|
888
1005
|
}
|
|
889
1006
|
lastQueueReason = why;
|
|
890
|
-
|
|
1007
|
+
nextText = `Queued \u2014 ${chalk5.bold(queuedChild.suite_name)} position ${q.position} for ${target} (${doneCount}/${totalMatched} suite(s) complete)`;
|
|
891
1008
|
} else {
|
|
892
1009
|
lastQueueReason = null;
|
|
893
|
-
|
|
1010
|
+
nextText = `Running suites \u2014 ${doneCount}/${totalMatched} complete...`;
|
|
1011
|
+
}
|
|
1012
|
+
if (TERMINAL.has(last.status)) {
|
|
1013
|
+
flush(null);
|
|
1014
|
+
break;
|
|
894
1015
|
}
|
|
895
|
-
|
|
1016
|
+
flush(nextText);
|
|
896
1017
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
897
1018
|
}
|
|
898
1019
|
if (!last || !TERMINAL.has(last.status)) {
|
|
@@ -906,7 +1027,9 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
906
1027
|
process.stdin.pause();
|
|
907
1028
|
}
|
|
908
1029
|
if (opts.output === "json") {
|
|
909
|
-
|
|
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");
|
|
910
1033
|
} else if (opts.output === "junit") {
|
|
911
1034
|
process.stdout.write(formatMultiSuiteJunit(last) + "\n");
|
|
912
1035
|
} else {
|
|
@@ -914,25 +1037,25 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
914
1037
|
Aggregate: ${last.status}
|
|
915
1038
|
`);
|
|
916
1039
|
for (const sr of last.suite_runs) {
|
|
917
|
-
const mark = sr.status === "passed" ?
|
|
918
|
-
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}`) : "";
|
|
919
1042
|
process.stdout.write(
|
|
920
1043
|
` ${mark} ${sr.suite_name}: ${sr.passed} passed, ${sr.failed} failed${link}
|
|
921
1044
|
`
|
|
922
1045
|
);
|
|
923
1046
|
if (sr.error_message) {
|
|
924
|
-
process.stdout.write(` ${
|
|
1047
|
+
process.stdout.write(` ${chalk5.red(sr.error_message)}
|
|
925
1048
|
`);
|
|
926
1049
|
}
|
|
927
1050
|
for (const cr of sr.case_runs ?? []) {
|
|
928
|
-
const caseMark = cr.status === "passed" ?
|
|
929
|
-
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)`) : "";
|
|
930
1053
|
process.stdout.write(
|
|
931
|
-
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${
|
|
1054
|
+
` ${caseMark} ${cr.case_name ?? "Case"} ${cr.status ?? "?"}${dur} ${chalk5.dim(cr.url)}
|
|
932
1055
|
`
|
|
933
1056
|
);
|
|
934
1057
|
if (cr.error_message && cr.status !== "passed") {
|
|
935
|
-
process.stdout.write(` ${
|
|
1058
|
+
process.stdout.write(` ${chalk5.red(cr.error_message)}
|
|
936
1059
|
`);
|
|
937
1060
|
}
|
|
938
1061
|
}
|
|
@@ -944,7 +1067,7 @@ Aggregate: ${last.status}
|
|
|
944
1067
|
}
|
|
945
1068
|
|
|
946
1069
|
// src/commands/run-test.ts
|
|
947
|
-
import
|
|
1070
|
+
import chalk6 from "chalk";
|
|
948
1071
|
async function runTest(testId, options) {
|
|
949
1072
|
const { token, baseUrl } = resolveAuth(options);
|
|
950
1073
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1009,18 +1132,23 @@ ${signal} received.
|
|
|
1009
1132
|
deviceCategory: options.deviceCategory,
|
|
1010
1133
|
device: options.device,
|
|
1011
1134
|
build: options.build,
|
|
1012
|
-
metadata: options.metadata
|
|
1135
|
+
metadata: options.metadata,
|
|
1136
|
+
platform: options.platform,
|
|
1137
|
+
app: options.app
|
|
1013
1138
|
});
|
|
1014
1139
|
sessionId = response.session_id;
|
|
1015
1140
|
runId = response.run_id;
|
|
1141
|
+
const target = describeRunTarget(response, options);
|
|
1016
1142
|
spinner.stop();
|
|
1017
1143
|
process.stderr.write(
|
|
1018
|
-
`${
|
|
1019
|
-
${
|
|
1020
|
-
${
|
|
1021
|
-
|
|
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}
|
|
1022
1149
|
`
|
|
1023
1150
|
);
|
|
1151
|
+
printBuildWarning(response.build_warning);
|
|
1024
1152
|
spinner.start("Waiting for device...");
|
|
1025
1153
|
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
1026
1154
|
const result = await pollUntilComplete(
|
|
@@ -1038,7 +1166,10 @@ ${chalk5.dim("Run:")} ${response.run_id}
|
|
|
1038
1166
|
process.stdin.setRawMode(false);
|
|
1039
1167
|
process.stdin.pause();
|
|
1040
1168
|
}
|
|
1041
|
-
outputResult(
|
|
1169
|
+
outputResult(
|
|
1170
|
+
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
1171
|
+
options
|
|
1172
|
+
);
|
|
1042
1173
|
process.exit(mapExitCode(result.status));
|
|
1043
1174
|
} catch (err) {
|
|
1044
1175
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
@@ -1064,7 +1195,7 @@ ${chalk5.dim("Run:")} ${response.run_id}
|
|
|
1064
1195
|
}
|
|
1065
1196
|
|
|
1066
1197
|
// src/commands/list-suites.ts
|
|
1067
|
-
import
|
|
1198
|
+
import chalk7 from "chalk";
|
|
1068
1199
|
async function listSuites(options) {
|
|
1069
1200
|
const { token, baseUrl } = resolveAuth(options);
|
|
1070
1201
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1086,21 +1217,21 @@ async function listSuites(options) {
|
|
|
1086
1217
|
console.log("No test suites found.");
|
|
1087
1218
|
return;
|
|
1088
1219
|
}
|
|
1089
|
-
console.log(
|
|
1220
|
+
console.log(chalk7.bold(`
|
|
1090
1221
|
${suites.length} suite(s)
|
|
1091
1222
|
`));
|
|
1092
1223
|
for (const s of suites) {
|
|
1093
|
-
const platform = s.platform ?
|
|
1094
|
-
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(", ")}]`) : "";
|
|
1095
1226
|
console.log(
|
|
1096
|
-
` ${
|
|
1227
|
+
` ${chalk7.cyan(s.id)} ${s.name}${platform}${tagsText} (${s.case_count} ${s.case_count === 1 ? "case" : "cases"})`
|
|
1097
1228
|
);
|
|
1098
1229
|
}
|
|
1099
1230
|
console.log();
|
|
1100
1231
|
}
|
|
1101
1232
|
|
|
1102
1233
|
// src/commands/list-apps.ts
|
|
1103
|
-
import
|
|
1234
|
+
import chalk8 from "chalk";
|
|
1104
1235
|
async function listApps(options) {
|
|
1105
1236
|
const { token, baseUrl } = resolveAuth(options);
|
|
1106
1237
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -1119,20 +1250,20 @@ async function listApps(options) {
|
|
|
1119
1250
|
console.log("No apps found.");
|
|
1120
1251
|
return;
|
|
1121
1252
|
}
|
|
1122
|
-
console.log(
|
|
1253
|
+
console.log(chalk8.bold(`
|
|
1123
1254
|
${apps.length} app(s)
|
|
1124
1255
|
`));
|
|
1125
1256
|
for (const app of apps) {
|
|
1126
|
-
const platform =
|
|
1127
|
-
const pkg2 =
|
|
1128
|
-
console.log(` ${
|
|
1129
|
-
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}`));
|
|
1130
1261
|
}
|
|
1131
1262
|
console.log();
|
|
1132
1263
|
}
|
|
1133
1264
|
|
|
1134
1265
|
// src/commands/stop.ts
|
|
1135
|
-
import
|
|
1266
|
+
import chalk9 from "chalk";
|
|
1136
1267
|
async function stopCommand(sessionId, opts, deps) {
|
|
1137
1268
|
const id = sessionId?.trim();
|
|
1138
1269
|
if (!id) throw new UsageError("a session id is required: vibeview stop <session-id>");
|
|
@@ -1143,7 +1274,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1143
1274
|
} catch (err) {
|
|
1144
1275
|
const message = err instanceof Error ? err.message : String(err);
|
|
1145
1276
|
if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
|
|
1146
|
-
console.error(
|
|
1277
|
+
console.error(chalk9.red(`error: ${message}`));
|
|
1147
1278
|
process.exitCode = errorExitCode(err);
|
|
1148
1279
|
return;
|
|
1149
1280
|
}
|
|
@@ -1176,7 +1307,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1176
1307
|
}
|
|
1177
1308
|
|
|
1178
1309
|
// src/commands/create-test.ts
|
|
1179
|
-
import
|
|
1310
|
+
import chalk10 from "chalk";
|
|
1180
1311
|
import { existsSync, readFileSync } from "fs";
|
|
1181
1312
|
async function readStdin() {
|
|
1182
1313
|
return new Promise((resolve, reject) => {
|
|
@@ -1217,6 +1348,18 @@ async function resolveSteps(options) {
|
|
|
1217
1348
|
}
|
|
1218
1349
|
throw new Error("No steps provided. Use --steps, --steps-file, or pipe JSON to stdin.");
|
|
1219
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
|
+
}
|
|
1220
1363
|
async function resolveAppId(client, appRef, platform) {
|
|
1221
1364
|
if (appRef.includes(".")) {
|
|
1222
1365
|
if (!platform) {
|
|
@@ -1238,6 +1381,7 @@ async function createTest(options) {
|
|
|
1238
1381
|
const client = new ApiClient(baseUrl, token);
|
|
1239
1382
|
try {
|
|
1240
1383
|
const steps = await resolveSteps(options);
|
|
1384
|
+
assertHasSteps(steps);
|
|
1241
1385
|
let appPublicId;
|
|
1242
1386
|
if (options.app) {
|
|
1243
1387
|
appPublicId = await resolveAppId(client, options.app, options.platform);
|
|
@@ -1271,20 +1415,20 @@ async function createTest(options) {
|
|
|
1271
1415
|
);
|
|
1272
1416
|
} else {
|
|
1273
1417
|
console.log(`
|
|
1274
|
-
${
|
|
1418
|
+
${chalk10.cyan(result.public_id)} ${result.name}`);
|
|
1275
1419
|
if (result.app_name) {
|
|
1276
|
-
console.log(` ${
|
|
1420
|
+
console.log(` ${chalk10.dim(`App: ${result.app_name}`)}`);
|
|
1277
1421
|
}
|
|
1278
1422
|
if (result.platform) {
|
|
1279
|
-
console.log(` ${
|
|
1423
|
+
console.log(` ${chalk10.dim(`Platform: ${result.platform}`)}`);
|
|
1280
1424
|
}
|
|
1281
|
-
console.log(` ${
|
|
1282
|
-
console.log(
|
|
1425
|
+
console.log(` ${chalk10.dim(`Steps: ${steps.length}`)}`);
|
|
1426
|
+
console.log(chalk10.dim(` ${baseUrl}/tests?tab=standalone
|
|
1283
1427
|
`));
|
|
1284
1428
|
}
|
|
1285
1429
|
} catch (err) {
|
|
1286
1430
|
if (options.output === "json") printJsonError(err);
|
|
1287
|
-
console.error(
|
|
1431
|
+
console.error(chalk10.red(err instanceof Error ? err.message : "Failed to create test"));
|
|
1288
1432
|
process.exit(
|
|
1289
1433
|
err instanceof ApiStatusError && err.status === 409 ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR
|
|
1290
1434
|
);
|
|
@@ -1368,7 +1512,7 @@ async function logout() {
|
|
|
1368
1512
|
}
|
|
1369
1513
|
|
|
1370
1514
|
// src/commands/dev-status.ts
|
|
1371
|
-
import
|
|
1515
|
+
import chalk11 from "chalk";
|
|
1372
1516
|
function buildLine(info) {
|
|
1373
1517
|
if (!info || typeof info.app_build_id !== "number") return null;
|
|
1374
1518
|
const version = info.app_version ? ` version ${info.app_version}${info.app_build_number ? ` (${info.app_build_number})` : ""}` : "";
|
|
@@ -1424,7 +1568,7 @@ async function devStatus(opts) {
|
|
|
1424
1568
|
console.error(` url: ${state.page_url}`);
|
|
1425
1569
|
console.error(` started: ${state.started_at}`);
|
|
1426
1570
|
}
|
|
1427
|
-
if (metro) console.error(
|
|
1571
|
+
if (metro) console.error(chalk11.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
|
|
1428
1572
|
process.exit(0);
|
|
1429
1573
|
}
|
|
1430
1574
|
|
|
@@ -1460,12 +1604,12 @@ async function devStop(opts) {
|
|
|
1460
1604
|
}
|
|
1461
1605
|
|
|
1462
1606
|
// src/commands/dev-reload.ts
|
|
1463
|
-
import
|
|
1607
|
+
import chalk12 from "chalk";
|
|
1464
1608
|
function fail(opts, code, message, exit) {
|
|
1465
1609
|
if (opts.json) {
|
|
1466
1610
|
console.log(JSON.stringify({ reloaded: false, code, message }));
|
|
1467
1611
|
} else {
|
|
1468
|
-
console.error(
|
|
1612
|
+
console.error(chalk12.red(`error: ${message}`));
|
|
1469
1613
|
}
|
|
1470
1614
|
process.exit(exit);
|
|
1471
1615
|
}
|
|
@@ -1518,7 +1662,7 @@ async function devReload(opts) {
|
|
|
1518
1662
|
api,
|
|
1519
1663
|
// Build output and the "reloaded in" line are human output: stderr, so
|
|
1520
1664
|
// stdout stays reserved for the --json result.
|
|
1521
|
-
log: (line) => console.error(
|
|
1665
|
+
log: (line) => console.error(chalk12.dim(line))
|
|
1522
1666
|
});
|
|
1523
1667
|
const started = Date.now();
|
|
1524
1668
|
try {
|
|
@@ -1539,10 +1683,10 @@ async function devReload(opts) {
|
|
|
1539
1683
|
}
|
|
1540
1684
|
|
|
1541
1685
|
// src/commands/builds.ts
|
|
1542
|
-
import
|
|
1686
|
+
import chalk14 from "chalk";
|
|
1543
1687
|
|
|
1544
1688
|
// src/lib/formatters/builds.ts
|
|
1545
|
-
import
|
|
1689
|
+
import chalk13 from "chalk";
|
|
1546
1690
|
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1547
1691
|
var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
1548
1692
|
function formatCostCell(costMicro, status) {
|
|
@@ -1550,13 +1694,13 @@ function formatCostCell(costMicro, status) {
|
|
|
1550
1694
|
return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
|
|
1551
1695
|
}
|
|
1552
1696
|
var STATUS_COLOR = {
|
|
1553
|
-
succeeded:
|
|
1554
|
-
failed:
|
|
1555
|
-
timed_out:
|
|
1556
|
-
infra_failed:
|
|
1557
|
-
canceled:
|
|
1558
|
-
running:
|
|
1559
|
-
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
|
|
1560
1704
|
};
|
|
1561
1705
|
function formatBuildsHuman(builds2) {
|
|
1562
1706
|
if (builds2.length === 0) {
|
|
@@ -1573,7 +1717,7 @@ function formatBuildsHuman(builds2) {
|
|
|
1573
1717
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1574
1718
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1575
1719
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
1576
|
-
const lines = [
|
|
1720
|
+
const lines = [chalk13.bold(join4(padCells(HEADERS)))];
|
|
1577
1721
|
for (const row of rows) {
|
|
1578
1722
|
const padded = padCells(row);
|
|
1579
1723
|
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
@@ -1632,7 +1776,7 @@ async function buildsLogs(id, opts, deps) {
|
|
|
1632
1776
|
console.log(JSON.stringify(logs));
|
|
1633
1777
|
} else {
|
|
1634
1778
|
if (logs.content) process.stdout.write(logs.content);
|
|
1635
|
-
console.error(
|
|
1779
|
+
console.error(chalk14.dim(`status: ${logs.status}`));
|
|
1636
1780
|
}
|
|
1637
1781
|
process.exitCode = 0;
|
|
1638
1782
|
return;
|
|
@@ -1729,7 +1873,7 @@ async function buildsDownload(id, opts, deps) {
|
|
|
1729
1873
|
if (opts.json) {
|
|
1730
1874
|
console.log(JSON.stringify(result));
|
|
1731
1875
|
} else {
|
|
1732
|
-
console.log(
|
|
1876
|
+
console.log(chalk14.green(`\u2713 saved ${result.path}`));
|
|
1733
1877
|
}
|
|
1734
1878
|
process.exitCode = 0;
|
|
1735
1879
|
} catch (err) {
|
|
@@ -1746,7 +1890,7 @@ async function buildsCancel(id, opts, deps) {
|
|
|
1746
1890
|
if (opts.json) {
|
|
1747
1891
|
console.log(JSON.stringify(build));
|
|
1748
1892
|
} else {
|
|
1749
|
-
console.log(`${
|
|
1893
|
+
console.log(`${chalk14.cyan(build.id)} ${build.status}`);
|
|
1750
1894
|
}
|
|
1751
1895
|
process.exitCode = 0;
|
|
1752
1896
|
} catch (err) {
|
|
@@ -1763,7 +1907,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1763
1907
|
if (opts.json) {
|
|
1764
1908
|
console.log(JSON.stringify(result));
|
|
1765
1909
|
} else {
|
|
1766
|
-
console.log(
|
|
1910
|
+
console.log(chalk14.green(`\u2713 deleted artifact for ${id}`));
|
|
1767
1911
|
}
|
|
1768
1912
|
process.exitCode = 0;
|
|
1769
1913
|
} catch (err) {
|
|
@@ -1774,11 +1918,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1774
1918
|
function printError(opts, err) {
|
|
1775
1919
|
const message = err instanceof Error ? err.message : String(err);
|
|
1776
1920
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1777
|
-
console.error(
|
|
1921
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1778
1922
|
}
|
|
1779
1923
|
|
|
1780
1924
|
// src/commands/submit.ts
|
|
1781
|
-
import
|
|
1925
|
+
import chalk15 from "chalk";
|
|
1782
1926
|
function defaultDeps2(client) {
|
|
1783
1927
|
return {
|
|
1784
1928
|
createSubmission: client.createSubmission.bind(client),
|
|
@@ -1802,7 +1946,7 @@ function submitConfigFor(cwd, platform) {
|
|
|
1802
1946
|
cfg = loadDevConfig(cwd);
|
|
1803
1947
|
} catch (err) {
|
|
1804
1948
|
console.error(
|
|
1805
|
-
|
|
1949
|
+
chalk15.yellow(
|
|
1806
1950
|
`warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
|
|
1807
1951
|
)
|
|
1808
1952
|
);
|
|
@@ -1950,12 +2094,12 @@ async function submitCommand(opts, deps) {
|
|
|
1950
2094
|
return;
|
|
1951
2095
|
}
|
|
1952
2096
|
if (!opts.json) {
|
|
1953
|
-
console.error(` ${
|
|
2097
|
+
console.error(` ${chalk15.cyan(sub.id)} ${chalk15.dim(`[${sub.platform}]`)}`);
|
|
1954
2098
|
}
|
|
1955
2099
|
const final = await followSubmission(sub, opts, d);
|
|
1956
2100
|
if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
|
|
1957
2101
|
console.error(
|
|
1958
|
-
|
|
2102
|
+
chalk15.dim("release status: draft \u2014 activate it from the Play Console when ready")
|
|
1959
2103
|
);
|
|
1960
2104
|
}
|
|
1961
2105
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
@@ -1986,11 +2130,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
|
1986
2130
|
});
|
|
1987
2131
|
emit(opts.json, { event: "preflight", ...result });
|
|
1988
2132
|
if (!opts.json) {
|
|
1989
|
-
const mark = result.ready ?
|
|
2133
|
+
const mark = result.ready ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
1990
2134
|
console.error(`${mark} ${result.message}`);
|
|
1991
2135
|
if (result.ready) {
|
|
1992
2136
|
console.error(
|
|
1993
|
-
|
|
2137
|
+
chalk15.dim(
|
|
1994
2138
|
` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
|
|
1995
2139
|
)
|
|
1996
2140
|
);
|
|
@@ -2024,7 +2168,7 @@ async function linkedIdentifier(platform, opts, deps) {
|
|
|
2024
2168
|
}
|
|
2025
2169
|
if (!app.package_name) return void 0;
|
|
2026
2170
|
if (!opts.json) {
|
|
2027
|
-
console.error(
|
|
2171
|
+
console.error(chalk15.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
|
|
2028
2172
|
}
|
|
2029
2173
|
return app.package_name;
|
|
2030
2174
|
}
|
|
@@ -2065,14 +2209,14 @@ async function followSubmission(created, opts, deps) {
|
|
|
2065
2209
|
if (opts.json) {
|
|
2066
2210
|
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
2067
2211
|
} else {
|
|
2068
|
-
console.error(
|
|
2212
|
+
console.error(chalk15.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
2069
2213
|
}
|
|
2070
2214
|
} else if (log.status === "running" && !printedRunning) {
|
|
2071
2215
|
printedRunning = true;
|
|
2072
2216
|
if (opts.json) {
|
|
2073
2217
|
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
2074
2218
|
} else {
|
|
2075
|
-
console.error(`${
|
|
2219
|
+
console.error(`${chalk15.cyan("\u25B6")} submitting`);
|
|
2076
2220
|
}
|
|
2077
2221
|
}
|
|
2078
2222
|
if (log.content) {
|
|
@@ -2114,7 +2258,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2114
2258
|
});
|
|
2115
2259
|
if (!opts.json) {
|
|
2116
2260
|
const ok = final.status === "succeeded";
|
|
2117
|
-
const mark = ok ?
|
|
2261
|
+
const mark = ok ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
2118
2262
|
const duration = formatDuration(final.started_at, final.finished_at);
|
|
2119
2263
|
let line = `${mark} ${final.status}`;
|
|
2120
2264
|
if (duration) line += ` (${duration})`;
|
|
@@ -2126,11 +2270,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2126
2270
|
function printError2(opts, err) {
|
|
2127
2271
|
const message = err instanceof Error ? err.message : String(err);
|
|
2128
2272
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2129
|
-
console.error(
|
|
2273
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2130
2274
|
}
|
|
2131
2275
|
|
|
2132
2276
|
// src/commands/versions.ts
|
|
2133
|
-
import
|
|
2277
|
+
import chalk16 from "chalk";
|
|
2134
2278
|
function defaultDeps3(client) {
|
|
2135
2279
|
return {
|
|
2136
2280
|
listAppVersions: client.listAppVersions.bind(client),
|
|
@@ -2180,7 +2324,7 @@ async function versionsSet(buildNumber, opts, deps) {
|
|
|
2180
2324
|
console.log(JSON.stringify(version));
|
|
2181
2325
|
} else {
|
|
2182
2326
|
console.log(
|
|
2183
|
-
|
|
2327
|
+
chalk16.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk16.dim(
|
|
2184
2328
|
" \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
|
|
2185
2329
|
)
|
|
2186
2330
|
);
|
|
@@ -2200,18 +2344,18 @@ function formatVersionsHuman(versions2) {
|
|
|
2200
2344
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2201
2345
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2202
2346
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2203
|
-
const lines = [
|
|
2347
|
+
const lines = [chalk16.bold(join4(padCells(HEADERS2)))];
|
|
2204
2348
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2205
2349
|
return lines.join("\n") + "\n";
|
|
2206
2350
|
}
|
|
2207
2351
|
function printError3(opts, err) {
|
|
2208
2352
|
const message = err instanceof Error ? err.message : String(err);
|
|
2209
2353
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2210
|
-
console.error(
|
|
2354
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2211
2355
|
}
|
|
2212
2356
|
|
|
2213
2357
|
// src/commands/whoami.ts
|
|
2214
|
-
import
|
|
2358
|
+
import chalk17 from "chalk";
|
|
2215
2359
|
function defaultDeps4(opts) {
|
|
2216
2360
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2217
2361
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -2223,6 +2367,15 @@ function effectiveRole(role, tokenRole) {
|
|
|
2223
2367
|
if (!tokenRole) return role;
|
|
2224
2368
|
return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
|
|
2225
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
|
+
}
|
|
2226
2379
|
async function whoAmI(opts, deps) {
|
|
2227
2380
|
const d = deps ?? defaultDeps4(opts);
|
|
2228
2381
|
try {
|
|
@@ -2233,27 +2386,33 @@ async function whoAmI(opts, deps) {
|
|
|
2233
2386
|
process.exitCode = 0;
|
|
2234
2387
|
return;
|
|
2235
2388
|
}
|
|
2236
|
-
console.log(`${
|
|
2389
|
+
console.log(`${chalk17.bold("Logged in as")} ${me.email}`);
|
|
2237
2390
|
if (me.memberships.length === 1) {
|
|
2238
|
-
console.log(`${
|
|
2391
|
+
console.log(`${chalk17.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
2239
2392
|
} else if (me.memberships.length > 1) {
|
|
2240
|
-
console.log(
|
|
2393
|
+
console.log(chalk17.bold("Organizations"));
|
|
2241
2394
|
for (const m of me.memberships) {
|
|
2242
2395
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
2243
2396
|
}
|
|
2244
2397
|
}
|
|
2245
|
-
console.log(`${
|
|
2246
|
-
console.log(`${
|
|
2247
|
-
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
|
+
}
|
|
2248
2407
|
if (me.is_superadmin) {
|
|
2249
|
-
console.log(
|
|
2408
|
+
console.log(chalk17.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
2250
2409
|
}
|
|
2251
|
-
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") {
|
|
2252
2411
|
console.log(
|
|
2253
|
-
|
|
2412
|
+
chalk17.yellow(
|
|
2254
2413
|
`
|
|
2255
|
-
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 ${
|
|
2256
|
-
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")}.`
|
|
2257
2416
|
)
|
|
2258
2417
|
);
|
|
2259
2418
|
}
|
|
@@ -2263,7 +2422,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2263
2422
|
if (opts.json) {
|
|
2264
2423
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2265
2424
|
}
|
|
2266
|
-
console.error(
|
|
2425
|
+
console.error(chalk17.red(`error: ${message}`));
|
|
2267
2426
|
process.exitCode = errorExitCode(err);
|
|
2268
2427
|
}
|
|
2269
2428
|
}
|
|
@@ -2271,7 +2430,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
2271
2430
|
// src/commands/env.ts
|
|
2272
2431
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
2273
2432
|
import { basename } from "path";
|
|
2274
|
-
import
|
|
2433
|
+
import chalk18 from "chalk";
|
|
2275
2434
|
import { password } from "@inquirer/prompts";
|
|
2276
2435
|
|
|
2277
2436
|
// src/lib/gitignore.ts
|
|
@@ -2327,7 +2486,7 @@ function defaultDeps5(client) {
|
|
|
2327
2486
|
isIgnored: (path) => isGitIgnored(path),
|
|
2328
2487
|
writeFile: writeSecureFile,
|
|
2329
2488
|
readFile: (path) => readFileAsync(path),
|
|
2330
|
-
warn: (message) => console.error(
|
|
2489
|
+
warn: (message) => console.error(chalk18.yellow(`warning: ${message}`))
|
|
2331
2490
|
};
|
|
2332
2491
|
}
|
|
2333
2492
|
async function envList(opts, deps) {
|
|
@@ -2359,7 +2518,7 @@ function formatEnvVarsHuman(variables) {
|
|
|
2359
2518
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2360
2519
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2361
2520
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2362
|
-
const lines = [
|
|
2521
|
+
const lines = [chalk18.bold(join4(padCells(HEADERS3)))];
|
|
2363
2522
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2364
2523
|
return lines.join("\n") + "\n";
|
|
2365
2524
|
}
|
|
@@ -2409,7 +2568,7 @@ function printSetResult(opts, result) {
|
|
|
2409
2568
|
if (opts.json) {
|
|
2410
2569
|
console.log(JSON.stringify(result));
|
|
2411
2570
|
} else {
|
|
2412
|
-
console.log(
|
|
2571
|
+
console.log(chalk18.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
2413
2572
|
}
|
|
2414
2573
|
}
|
|
2415
2574
|
async function envRm(name, opts, deps) {
|
|
@@ -2419,7 +2578,7 @@ async function envRm(name, opts, deps) {
|
|
|
2419
2578
|
if (opts.json) {
|
|
2420
2579
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2421
2580
|
} else {
|
|
2422
|
-
console.log(
|
|
2581
|
+
console.log(chalk18.green(`\u2713 removed ${name}`));
|
|
2423
2582
|
}
|
|
2424
2583
|
process.exitCode = 0;
|
|
2425
2584
|
} catch (err) {
|
|
@@ -2451,7 +2610,7 @@ async function envPull(opts, deps) {
|
|
|
2451
2610
|
if (opts.json) {
|
|
2452
2611
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
2453
2612
|
} else {
|
|
2454
|
-
console.log(
|
|
2613
|
+
console.log(chalk18.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
2455
2614
|
}
|
|
2456
2615
|
process.exitCode = 0;
|
|
2457
2616
|
} catch (err) {
|
|
@@ -2490,7 +2649,7 @@ function escapeEnvValue(value) {
|
|
|
2490
2649
|
function printError4(opts, err) {
|
|
2491
2650
|
const message = err instanceof Error ? err.message : String(err);
|
|
2492
2651
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2493
|
-
console.error(
|
|
2652
|
+
console.error(chalk18.red(`error: ${message}`));
|
|
2494
2653
|
}
|
|
2495
2654
|
|
|
2496
2655
|
// src/commands/credentials.ts
|
|
@@ -2502,7 +2661,7 @@ import {
|
|
|
2502
2661
|
} from "fs/promises";
|
|
2503
2662
|
import { mkdirSync } from "fs";
|
|
2504
2663
|
import { basename as basename2, extname, join } from "path";
|
|
2505
|
-
import
|
|
2664
|
+
import chalk19 from "chalk";
|
|
2506
2665
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
2507
2666
|
function clientFor2(opts) {
|
|
2508
2667
|
const { token, baseUrl } = resolveAuth(opts);
|
|
@@ -2575,7 +2734,7 @@ function defaultDeps6(client) {
|
|
|
2575
2734
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
2576
2735
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
2577
2736
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
2578
|
-
warn: (message) => console.error(
|
|
2737
|
+
warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
|
|
2579
2738
|
};
|
|
2580
2739
|
}
|
|
2581
2740
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -2644,7 +2803,7 @@ async function credentialsAdd(opts, deps) {
|
|
|
2644
2803
|
if (opts.json) {
|
|
2645
2804
|
console.log(JSON.stringify(result));
|
|
2646
2805
|
} else {
|
|
2647
|
-
console.log(
|
|
2806
|
+
console.log(chalk19.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
2648
2807
|
}
|
|
2649
2808
|
process.exitCode = 0;
|
|
2650
2809
|
} catch (err) {
|
|
@@ -2702,20 +2861,20 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2702
2861
|
}
|
|
2703
2862
|
if (opts.json) {
|
|
2704
2863
|
} else {
|
|
2705
|
-
console.log(
|
|
2864
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (android)`));
|
|
2706
2865
|
console.log("");
|
|
2707
|
-
console.log(` Keystore saved to: ${
|
|
2708
|
-
console.log(` Key alias: ${
|
|
2709
|
-
console.log(` Store password: ${
|
|
2710
|
-
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)}`);
|
|
2711
2870
|
console.log("");
|
|
2712
2871
|
console.log(
|
|
2713
|
-
|
|
2872
|
+
chalk19.yellow(
|
|
2714
2873
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
2715
2874
|
)
|
|
2716
2875
|
);
|
|
2717
2876
|
console.log(
|
|
2718
|
-
|
|
2877
|
+
chalk19.yellow(
|
|
2719
2878
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
2720
2879
|
)
|
|
2721
2880
|
);
|
|
@@ -2741,19 +2900,19 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
2741
2900
|
if (opts.json) {
|
|
2742
2901
|
console.log(JSON.stringify(result));
|
|
2743
2902
|
} else {
|
|
2744
|
-
console.log(
|
|
2903
|
+
console.log(chalk19.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
2745
2904
|
console.log("");
|
|
2746
|
-
console.log(` Scope: ${
|
|
2905
|
+
console.log(` Scope: ${chalk19.bold(result.app_public_id ?? "org-wide")}`);
|
|
2747
2906
|
const expires = formatExpires(result);
|
|
2748
2907
|
if (expires) {
|
|
2749
|
-
console.log(` Expires: ${
|
|
2908
|
+
console.log(` Expires: ${chalk19.bold(expires)}`);
|
|
2750
2909
|
}
|
|
2751
2910
|
console.log("");
|
|
2752
2911
|
console.log(
|
|
2753
2912
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
2754
2913
|
);
|
|
2755
2914
|
console.log(
|
|
2756
|
-
` Run ${
|
|
2915
|
+
` Run ${chalk19.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
2757
2916
|
);
|
|
2758
2917
|
}
|
|
2759
2918
|
process.exitCode = 0;
|
|
@@ -2819,17 +2978,17 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
2819
2978
|
await d.chmod(path, 384);
|
|
2820
2979
|
}
|
|
2821
2980
|
if (!opts.json) {
|
|
2822
|
-
console.log(
|
|
2981
|
+
console.log(chalk19.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
2823
2982
|
console.log("");
|
|
2824
|
-
console.log(` Saved to: ${
|
|
2983
|
+
console.log(` Saved to: ${chalk19.bold(savedTo)}`);
|
|
2825
2984
|
if (profileSavedTo !== null) {
|
|
2826
|
-
console.log(` Profile: ${
|
|
2985
|
+
console.log(` Profile: ${chalk19.bold(profileSavedTo)}`);
|
|
2827
2986
|
}
|
|
2828
2987
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
2829
|
-
console.log(` ${key}: ${
|
|
2988
|
+
console.log(` ${key}: ${chalk19.bold(value)}`);
|
|
2830
2989
|
}
|
|
2831
2990
|
console.log("");
|
|
2832
|
-
console.log(
|
|
2991
|
+
console.log(chalk19.yellow(" This download is recorded in your organization's audit log."));
|
|
2833
2992
|
}
|
|
2834
2993
|
process.exitCode = 0;
|
|
2835
2994
|
} catch (err) {
|
|
@@ -2860,13 +3019,13 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2860
3019
|
if (opts.json) {
|
|
2861
3020
|
console.log(JSON.stringify(result));
|
|
2862
3021
|
} else {
|
|
2863
|
-
console.log(
|
|
3022
|
+
console.log(chalk19.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2864
3023
|
console.log("");
|
|
2865
|
-
console.log(` Key ID: ${
|
|
2866
|
-
console.log(` Issuer ID: ${
|
|
3024
|
+
console.log(` Key ID: ${chalk19.bold(result.key_id)}`);
|
|
3025
|
+
console.log(` Issuer ID: ${chalk19.bold(result.issuer_id)}`);
|
|
2867
3026
|
console.log("");
|
|
2868
3027
|
console.log(
|
|
2869
|
-
|
|
3028
|
+
chalk19.yellow(
|
|
2870
3029
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2871
3030
|
)
|
|
2872
3031
|
);
|
|
@@ -2901,7 +3060,7 @@ function formatAscKeysHuman(keys) {
|
|
|
2901
3060
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2902
3061
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2903
3062
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2904
|
-
const lines = [
|
|
3063
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
2905
3064
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2906
3065
|
return lines.join("\n") + "\n";
|
|
2907
3066
|
}
|
|
@@ -2912,7 +3071,7 @@ async function ascKeyRm(name, opts, deps) {
|
|
|
2912
3071
|
if (opts.json) {
|
|
2913
3072
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2914
3073
|
} else {
|
|
2915
|
-
console.log(
|
|
3074
|
+
console.log(chalk19.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2916
3075
|
}
|
|
2917
3076
|
process.exitCode = 0;
|
|
2918
3077
|
} catch (err) {
|
|
@@ -2937,12 +3096,12 @@ async function playKeyAdd(opts, deps) {
|
|
|
2937
3096
|
if (opts.json) {
|
|
2938
3097
|
console.log(JSON.stringify(result));
|
|
2939
3098
|
} else {
|
|
2940
|
-
console.log(
|
|
3099
|
+
console.log(chalk19.green(`\u2713 added Google Play key ${result.name}`));
|
|
2941
3100
|
console.log("");
|
|
2942
|
-
console.log(` Service account: ${
|
|
3101
|
+
console.log(` Service account: ${chalk19.bold(result.client_email)}`);
|
|
2943
3102
|
console.log("");
|
|
2944
3103
|
console.log(
|
|
2945
|
-
|
|
3104
|
+
chalk19.yellow(
|
|
2946
3105
|
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2947
3106
|
)
|
|
2948
3107
|
);
|
|
@@ -2977,7 +3136,7 @@ function formatPlayKeysHuman(keys) {
|
|
|
2977
3136
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2978
3137
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2979
3138
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
2980
|
-
const lines = [
|
|
3139
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
2981
3140
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
2982
3141
|
return lines.join("\n") + "\n";
|
|
2983
3142
|
}
|
|
@@ -2988,7 +3147,7 @@ async function playKeyRm(name, opts, deps) {
|
|
|
2988
3147
|
if (opts.json) {
|
|
2989
3148
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2990
3149
|
} else {
|
|
2991
|
-
console.log(
|
|
3150
|
+
console.log(chalk19.green(`\u2713 removed Google Play key ${name}`));
|
|
2992
3151
|
}
|
|
2993
3152
|
process.exitCode = 0;
|
|
2994
3153
|
} catch (err) {
|
|
@@ -3057,7 +3216,7 @@ function formatCredentialsHuman(credentials2) {
|
|
|
3057
3216
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3058
3217
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3059
3218
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3060
|
-
const lines = [
|
|
3219
|
+
const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
|
|
3061
3220
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3062
3221
|
return lines.join("\n") + "\n";
|
|
3063
3222
|
}
|
|
@@ -3068,7 +3227,7 @@ async function credentialsRm(name, opts, deps) {
|
|
|
3068
3227
|
if (opts.json) {
|
|
3069
3228
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3070
3229
|
} else {
|
|
3071
|
-
console.log(
|
|
3230
|
+
console.log(chalk19.green(`\u2713 removed ${name}`));
|
|
3072
3231
|
}
|
|
3073
3232
|
process.exitCode = 0;
|
|
3074
3233
|
} catch (err) {
|
|
@@ -3080,16 +3239,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3080
3239
|
const message = err instanceof Error ? err.message : String(err);
|
|
3081
3240
|
if (opts.json) {
|
|
3082
3241
|
console.log(JSON.stringify({ event: "error", message }));
|
|
3083
|
-
console.error(
|
|
3242
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3084
3243
|
return;
|
|
3085
3244
|
}
|
|
3086
|
-
console.error(
|
|
3245
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3087
3246
|
console.error(
|
|
3088
|
-
|
|
3247
|
+
chalk19.red(
|
|
3089
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.`
|
|
3090
3249
|
)
|
|
3091
3250
|
);
|
|
3092
|
-
console.error(
|
|
3251
|
+
console.error(chalk19.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
3093
3252
|
console.error("");
|
|
3094
3253
|
console.error(` Key alias: ${result.key_alias}`);
|
|
3095
3254
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -3103,11 +3262,11 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3103
3262
|
function printError5(opts, err) {
|
|
3104
3263
|
const message = err instanceof Error ? err.message : String(err);
|
|
3105
3264
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3106
|
-
console.error(
|
|
3265
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
3107
3266
|
}
|
|
3108
3267
|
|
|
3109
3268
|
// src/commands/webhooks.ts
|
|
3110
|
-
import
|
|
3269
|
+
import chalk20 from "chalk";
|
|
3111
3270
|
function clientFor3(opts) {
|
|
3112
3271
|
const { token, baseUrl } = resolveAuth(opts);
|
|
3113
3272
|
return new ApiClient(baseUrl, token);
|
|
@@ -3138,14 +3297,14 @@ async function webhooksAdd(opts, deps) {
|
|
|
3138
3297
|
if (opts.json) {
|
|
3139
3298
|
console.log(JSON.stringify(result));
|
|
3140
3299
|
} else {
|
|
3141
|
-
console.log(
|
|
3300
|
+
console.log(chalk20.green(`\u2713 added ${result.id} ${result.url}`));
|
|
3142
3301
|
console.log("");
|
|
3143
|
-
console.log(` Signing secret: ${
|
|
3302
|
+
console.log(` Signing secret: ${chalk20.bold(result.secret)}`);
|
|
3144
3303
|
if (opts.secret !== void 0) {
|
|
3145
|
-
console.log(
|
|
3304
|
+
console.log(chalk20.yellow(" (This is the secret you provided.)"));
|
|
3146
3305
|
}
|
|
3147
3306
|
console.log(
|
|
3148
|
-
|
|
3307
|
+
chalk20.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
3149
3308
|
);
|
|
3150
3309
|
}
|
|
3151
3310
|
process.exitCode = 0;
|
|
@@ -3176,7 +3335,7 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
3176
3335
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3177
3336
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3178
3337
|
const join4 = (cells) => cells.join(" ").trimEnd();
|
|
3179
|
-
const lines = [
|
|
3338
|
+
const lines = [chalk20.bold(join4(padCells(HEADERS3)))];
|
|
3180
3339
|
for (const row of rows) lines.push(join4(padCells(row)));
|
|
3181
3340
|
return lines.join("\n") + "\n";
|
|
3182
3341
|
}
|
|
@@ -3202,7 +3361,7 @@ async function webhooksRm(id, opts, deps) {
|
|
|
3202
3361
|
if (opts.json) {
|
|
3203
3362
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
3204
3363
|
} else {
|
|
3205
|
-
console.log(
|
|
3364
|
+
console.log(chalk20.green(`\u2713 removed ${id}`));
|
|
3206
3365
|
}
|
|
3207
3366
|
process.exitCode = 0;
|
|
3208
3367
|
} catch (err) {
|
|
@@ -3235,13 +3394,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3235
3394
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
3236
3395
|
if (ld.status === "succeeded") {
|
|
3237
3396
|
if (!opts.json) {
|
|
3238
|
-
console.log(
|
|
3397
|
+
console.log(chalk20.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
3239
3398
|
}
|
|
3240
3399
|
process.exitCode = 0;
|
|
3241
3400
|
} else {
|
|
3242
3401
|
if (!opts.json) {
|
|
3243
3402
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
3244
|
-
console.error(
|
|
3403
|
+
console.error(chalk20.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
|
|
3245
3404
|
}
|
|
3246
3405
|
process.exitCode = 1;
|
|
3247
3406
|
}
|
|
@@ -3250,7 +3409,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3250
3409
|
}
|
|
3251
3410
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
3252
3411
|
console.error(
|
|
3253
|
-
|
|
3412
|
+
chalk20.yellow(
|
|
3254
3413
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
3255
3414
|
)
|
|
3256
3415
|
);
|
|
@@ -3263,7 +3422,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3263
3422
|
function printError6(opts, err) {
|
|
3264
3423
|
const message = err instanceof Error ? err.message : String(err);
|
|
3265
3424
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3266
|
-
console.error(
|
|
3425
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3267
3426
|
}
|
|
3268
3427
|
|
|
3269
3428
|
// src/commands/list-devices.ts
|
|
@@ -3361,7 +3520,7 @@ import { dirname, join as join2 } from "path";
|
|
|
3361
3520
|
import { fileURLToPath } from "url";
|
|
3362
3521
|
import { spawnSync } from "child_process";
|
|
3363
3522
|
import { confirm } from "@inquirer/prompts";
|
|
3364
|
-
import
|
|
3523
|
+
import chalk21 from "chalk";
|
|
3365
3524
|
function resolveShippedSkillPath(fromDir) {
|
|
3366
3525
|
for (const rel of ["..", join2("..", "..")]) {
|
|
3367
3526
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -3389,23 +3548,23 @@ function registerMcp() {
|
|
|
3389
3548
|
encoding: "utf8"
|
|
3390
3549
|
});
|
|
3391
3550
|
if (res.status === 0) {
|
|
3392
|
-
console.log(
|
|
3551
|
+
console.log(chalk21.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
3393
3552
|
return;
|
|
3394
3553
|
}
|
|
3395
3554
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
3396
3555
|
if (out.includes("already exists")) {
|
|
3397
|
-
console.log(
|
|
3556
|
+
console.log(chalk21.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
3398
3557
|
return;
|
|
3399
3558
|
}
|
|
3400
|
-
console.error(
|
|
3401
|
-
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"));
|
|
3402
3561
|
}
|
|
3403
3562
|
async function agentSetup(opts) {
|
|
3404
3563
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3405
3564
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
3406
3565
|
if (!shipped) {
|
|
3407
3566
|
console.error(
|
|
3408
|
-
|
|
3567
|
+
chalk21.red(
|
|
3409
3568
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
3410
3569
|
)
|
|
3411
3570
|
);
|
|
@@ -3414,9 +3573,9 @@ async function agentSetup(opts) {
|
|
|
3414
3573
|
}
|
|
3415
3574
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
3416
3575
|
const result = installSkill(shipped, targetDir);
|
|
3417
|
-
console.log(
|
|
3576
|
+
console.log(chalk21.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
3418
3577
|
console.log(
|
|
3419
|
-
|
|
3578
|
+
chalk21.dim(
|
|
3420
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."
|
|
3421
3580
|
)
|
|
3422
3581
|
);
|
|
@@ -3424,7 +3583,7 @@ async function agentSetup(opts) {
|
|
|
3424
3583
|
if (!claudeCliAvailable()) {
|
|
3425
3584
|
if (opts.mcp === true) {
|
|
3426
3585
|
console.error(
|
|
3427
|
-
|
|
3586
|
+
chalk21.yellow(
|
|
3428
3587
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
3429
3588
|
)
|
|
3430
3589
|
);
|
|
@@ -3438,7 +3597,7 @@ async function agentSetup(opts) {
|
|
|
3438
3597
|
}
|
|
3439
3598
|
console.log(
|
|
3440
3599
|
`
|
|
3441
|
-
Suggested line for your project's ${
|
|
3600
|
+
Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bold(
|
|
3442
3601
|
"AGENTS.md"
|
|
3443
3602
|
)} for other agents):
|
|
3444
3603
|
`
|
|
@@ -3446,7 +3605,7 @@ Suggested line for your project's ${chalk20.bold("CLAUDE.md")} (or ${chalk20.bol
|
|
|
3446
3605
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
3447
3606
|
`);
|
|
3448
3607
|
console.log(
|
|
3449
|
-
|
|
3608
|
+
chalk21.dim(
|
|
3450
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"
|
|
3451
3610
|
)
|
|
3452
3611
|
);
|
|
@@ -3568,11 +3727,25 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
|
|
|
3568
3727
|
).option(
|
|
3569
3728
|
"--device <device_id>",
|
|
3570
3729
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3730
|
+
).option(
|
|
3731
|
+
"--platform <platform>",
|
|
3732
|
+
"Run the suite on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
|
|
3733
|
+
).option(
|
|
3734
|
+
"--app <app-id>",
|
|
3735
|
+
"Run the suite against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3571
3736
|
).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((suiteId, opts) => {
|
|
3572
3737
|
if (opts.device && opts.deviceCategory) {
|
|
3573
3738
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3574
3739
|
process.exit(2);
|
|
3575
3740
|
}
|
|
3741
|
+
try {
|
|
3742
|
+
parseRunPlatform(opts.platform);
|
|
3743
|
+
refusePlatformWithApp(opts);
|
|
3744
|
+
} catch (err) {
|
|
3745
|
+
process.stderr.write(`error: ${err.message}
|
|
3746
|
+
`);
|
|
3747
|
+
process.exit(2);
|
|
3748
|
+
}
|
|
3576
3749
|
const metadata = resolveMetadata(opts);
|
|
3577
3750
|
return runSuite(suiteId, {
|
|
3578
3751
|
...opts,
|
|
@@ -3601,6 +3774,13 @@ program.command("run-suites").description("Run all suites matching a tag filter"
|
|
|
3601
3774
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3602
3775
|
process.exit(2);
|
|
3603
3776
|
}
|
|
3777
|
+
try {
|
|
3778
|
+
parseRunPlatform(opts.platform);
|
|
3779
|
+
} catch (err) {
|
|
3780
|
+
process.stderr.write(`error: ${err.message}
|
|
3781
|
+
`);
|
|
3782
|
+
process.exit(2);
|
|
3783
|
+
}
|
|
3604
3784
|
const metadata = resolveMetadata(opts);
|
|
3605
3785
|
return runSuites({
|
|
3606
3786
|
...opts,
|
|
@@ -3616,11 +3796,25 @@ program.command("run-test <test-id>").description("Run a single test case").opti
|
|
|
3616
3796
|
).option(
|
|
3617
3797
|
"--device <device_id>",
|
|
3618
3798
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3799
|
+
).option(
|
|
3800
|
+
"--platform <platform>",
|
|
3801
|
+
"Run the test on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
|
|
3802
|
+
).option(
|
|
3803
|
+
"--app <app-id>",
|
|
3804
|
+
"Run the test against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3619
3805
|
).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((testId, opts) => {
|
|
3620
3806
|
if (opts.device && opts.deviceCategory) {
|
|
3621
3807
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3622
3808
|
process.exit(2);
|
|
3623
3809
|
}
|
|
3810
|
+
try {
|
|
3811
|
+
parseRunPlatform(opts.platform);
|
|
3812
|
+
refusePlatformWithApp(opts);
|
|
3813
|
+
} catch (err) {
|
|
3814
|
+
process.stderr.write(`error: ${err.message}
|
|
3815
|
+
`);
|
|
3816
|
+
process.exit(2);
|
|
3817
|
+
}
|
|
3624
3818
|
const metadata = resolveMetadata(opts);
|
|
3625
3819
|
return runTest(testId, {
|
|
3626
3820
|
...opts,
|
|
@@ -3669,7 +3863,10 @@ program.command("dev").description(
|
|
|
3669
3863
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--web-url <url>", "Dashboard URL if different from the API URL (local dev)").option(
|
|
3670
3864
|
"--platform <platform>",
|
|
3671
3865
|
"ios, android, tvos, androidtv, or roku (first run is interactive otherwise)"
|
|
3672
|
-
).option("--app <app_id>", "Uploaded debug build to run (see `vibeview list-apps`)").option(
|
|
3866
|
+
).option("--app <app_id>", "Uploaded debug build to run (see `vibeview list-apps`)").option(
|
|
3867
|
+
"--metro-port <port>",
|
|
3868
|
+
"Local Metro port (default: 8081, or vibeview.json); remembered for you in .vibeview/local.json"
|
|
3869
|
+
).option(
|
|
3673
3870
|
"--device-category <category>",
|
|
3674
3871
|
"phone or tablet (default: phone; TV platforms always use tv)",
|
|
3675
3872
|
parseDeviceCategory
|
|
@@ -3831,7 +4028,7 @@ program.command("dev-reload").description(
|
|
|
3831
4028
|
program.command("mcp").description(
|
|
3832
4029
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3833
4030
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3834
|
-
const { runMcpServer } = await import("./server-
|
|
4031
|
+
const { runMcpServer } = await import("./server-GPFOZOFL.js");
|
|
3835
4032
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
3836
4033
|
});
|
|
3837
4034
|
program.command("agent-setup").description(
|