vibeview 0.37.1 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/{chunk-NJ2IXB5H.js → chunk-AV674D7T.js} +3 -3
- package/dist/{chunk-QRJAFBH3.js → chunk-MYXUGCR5.js} +41 -5
- package/dist/cli.js +385 -183
- package/dist/{server-DF3CDWL4.js → server-FMU3HQWF.js} +193 -4
- package/dist/{upload-app-BPTGBDQB.js → upload-app-UHJLFQKZ.js} +1 -1
- package/package.json +2 -1
- package/skills/vibeview-agent/SKILL.md +6 -2
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
readMetroState,
|
|
26
26
|
recordDevUse,
|
|
27
27
|
runAgentVerb
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-AV674D7T.js";
|
|
29
29
|
import {
|
|
30
30
|
ApiClient,
|
|
31
31
|
ApiStatusError,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
resolveAuth,
|
|
47
47
|
saveConfig,
|
|
48
48
|
uploadApp
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-MYXUGCR5.js";
|
|
50
50
|
|
|
51
51
|
// src/cli.ts
|
|
52
52
|
import { createRequire } from "module";
|
|
@@ -90,6 +90,7 @@ function resultCases(result) {
|
|
|
90
90
|
error_kind_description: failedStep?.error_kind_description ?? null,
|
|
91
91
|
screenshot_signed_url: failedStep?.screenshot_signed_url ?? null,
|
|
92
92
|
url: result.url,
|
|
93
|
+
quarantined: result.quarantined,
|
|
93
94
|
steps_not_checked: runStepsNotChecked(result),
|
|
94
95
|
not_checked_steps: notCheckedSteps(result.steps).map((s) => ({
|
|
95
96
|
step: s.step,
|
|
@@ -117,7 +118,7 @@ function failureLines(failure) {
|
|
|
117
118
|
}
|
|
118
119
|
function formatHuman(result, verbose) {
|
|
119
120
|
const lines = [];
|
|
120
|
-
const icon = result.status === "passed" ? chalk.green("PASS") : chalk.red("FAIL");
|
|
121
|
+
const icon = result.status === "passed" ? chalk.green("PASS") : result.status === "failed" && result.quarantined === true ? chalk.yellow("FAIL (quarantined)") : chalk.red("FAIL");
|
|
121
122
|
const duration = result.duration_ms ? `${(result.duration_ms / 1e3).toFixed(1)}s` : "-";
|
|
122
123
|
lines.push("");
|
|
123
124
|
const title = result.suite_name || result.case_name || result.run_id;
|
|
@@ -138,7 +139,7 @@ function formatHuman(result, verbose) {
|
|
|
138
139
|
if (suiteCases.length > 0) {
|
|
139
140
|
lines.push("");
|
|
140
141
|
for (const tr of suiteCases) {
|
|
141
|
-
const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
|
|
142
|
+
const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "failed" && tr.quarantined === true ? chalk.yellow(" FAIL (quarantined)") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
|
|
142
143
|
const caseDuration = tr.duration_ms ? `${(tr.duration_ms / 1e3).toFixed(1)}s` : "-";
|
|
143
144
|
const caseSuffix = notCheckedSuffix(tr.status, tr.steps_not_checked);
|
|
144
145
|
lines.push(
|
|
@@ -214,21 +215,24 @@ function caseDetailLines(c) {
|
|
|
214
215
|
if (out.length) lines.push(` <system-out>${escapeXml(out.join("\n"))}</system-out>`);
|
|
215
216
|
return lines;
|
|
216
217
|
}
|
|
218
|
+
var isQuarantinedFailure = (t) => t.status === "failed" && t.quarantined === true;
|
|
217
219
|
function formatJunit(result) {
|
|
218
220
|
const suiteName = escapeXml(result.suite_name || result.case_name || result.run_id);
|
|
219
221
|
const cases = resultCases(result);
|
|
220
222
|
const tests = cases.length;
|
|
221
|
-
const failures = cases.filter((t) => t.status === "failed").length;
|
|
223
|
+
const failures = cases.filter((t) => t.status === "failed" && !isQuarantinedFailure(t)).length;
|
|
222
224
|
const errors = cases.filter((t) => t.status === "error").length;
|
|
225
|
+
const skipped = cases.filter(isQuarantinedFailure).length;
|
|
226
|
+
const skippedAttr = skipped > 0 ? ` skipped="${skipped}"` : "";
|
|
223
227
|
const totalTime = result.duration_ms ? (result.duration_ms / 1e3).toFixed(1) : "0";
|
|
224
228
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
225
229
|
const lines = [];
|
|
226
230
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
227
231
|
lines.push(
|
|
228
|
-
`<testsuites name="vibeview" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}">`
|
|
232
|
+
`<testsuites name="vibeview" tests="${tests}" failures="${failures}" errors="${errors}"${skippedAttr} time="${totalTime}">`
|
|
229
233
|
);
|
|
230
234
|
lines.push(
|
|
231
|
-
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
235
|
+
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}"${skippedAttr} time="${totalTime}" timestamp="${timestamp}">`
|
|
232
236
|
);
|
|
233
237
|
if (result.url || result.build_warning) {
|
|
234
238
|
lines.push(" <properties>");
|
|
@@ -251,7 +255,10 @@ function formatJunit(result) {
|
|
|
251
255
|
continue;
|
|
252
256
|
}
|
|
253
257
|
lines.push(` <testcase name="${caseName}" classname="${suiteName}" time="${caseTime}">`);
|
|
254
|
-
if (tr
|
|
258
|
+
if (isQuarantinedFailure(tr)) {
|
|
259
|
+
const msg = escapeXml(tr.error_message || "Test failed");
|
|
260
|
+
lines.push(` <skipped message="quarantined: ${msg}" />`);
|
|
261
|
+
} else if (tr.status === "failed") {
|
|
255
262
|
const msg = escapeXml(tr.error_message || "Test failed");
|
|
256
263
|
lines.push(` <failure message="${msg}" type="TestFailure">${msg}</failure>`);
|
|
257
264
|
} else if (tr.status !== "passed") {
|
|
@@ -324,6 +331,13 @@ function formatMultiSuiteJunit(result, opts = {}) {
|
|
|
324
331
|
url: c.url || void 0,
|
|
325
332
|
steps_not_checked: c.steps_not_checked
|
|
326
333
|
};
|
|
334
|
+
if (c.status === "failed" && c.quarantined === true) {
|
|
335
|
+
return {
|
|
336
|
+
...base,
|
|
337
|
+
verdict: "skipped",
|
|
338
|
+
message: `quarantined: ${c.error_message || "Test failed"}`
|
|
339
|
+
};
|
|
340
|
+
}
|
|
327
341
|
switch (c.status) {
|
|
328
342
|
case "passed":
|
|
329
343
|
return { ...base, verdict: "passed" };
|
|
@@ -699,10 +713,26 @@ function mapExitCode(status) {
|
|
|
699
713
|
if (status === "failed") return EXIT_TEST_FAIL;
|
|
700
714
|
return EXIT_INFRA_ERROR;
|
|
701
715
|
}
|
|
716
|
+
function isQuarantinedFailure2(result) {
|
|
717
|
+
return result.status === "failed" && result.quarantined === true;
|
|
718
|
+
}
|
|
719
|
+
function runExitCode(result) {
|
|
720
|
+
return isQuarantinedFailure2(result) ? EXIT_PASS : mapExitCode(result.status);
|
|
721
|
+
}
|
|
702
722
|
|
|
703
723
|
// src/lib/run-target.ts
|
|
704
724
|
import chalk3 from "chalk";
|
|
705
725
|
var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
726
|
+
var SUITE_FILTER_PLATFORMS = [...RUN_PLATFORMS, "roku"];
|
|
727
|
+
function parseSuitePlatformFilter(value) {
|
|
728
|
+
if (value === void 0) return void 0;
|
|
729
|
+
if (!SUITE_FILTER_PLATFORMS.includes(value)) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`--platform must be one of ${SUITE_FILTER_PLATFORMS.join(", ")}, got "${value}"`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return value;
|
|
735
|
+
}
|
|
706
736
|
function parseRunPlatform(value) {
|
|
707
737
|
if (value === void 0) return void 0;
|
|
708
738
|
if (!RUN_PLATFORMS.includes(value)) {
|
|
@@ -802,6 +832,7 @@ ${signal} received.
|
|
|
802
832
|
device: options.device,
|
|
803
833
|
build: options.build,
|
|
804
834
|
metadata: options.metadata,
|
|
835
|
+
retryFailed: options.retry === false ? false : void 0,
|
|
805
836
|
platform: options.platform,
|
|
806
837
|
app: options.app
|
|
807
838
|
});
|
|
@@ -838,7 +869,7 @@ ${signal} received.
|
|
|
838
869
|
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
839
870
|
options
|
|
840
871
|
);
|
|
841
|
-
process.exit(
|
|
872
|
+
process.exit(runExitCode(result));
|
|
842
873
|
} catch (err) {
|
|
843
874
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
844
875
|
outputRunError(err, options, {
|
|
@@ -928,7 +959,8 @@ ${signal} received.
|
|
|
928
959
|
device_category: opts.device ? void 0 : opts.deviceCategory,
|
|
929
960
|
device_id: opts.device,
|
|
930
961
|
build_id: opts.build,
|
|
931
|
-
metadata: opts.metadata
|
|
962
|
+
metadata: opts.metadata,
|
|
963
|
+
retry_failed: opts.retry === false ? false : void 0
|
|
932
964
|
});
|
|
933
965
|
} catch (err) {
|
|
934
966
|
spinner.stop();
|
|
@@ -1206,6 +1238,7 @@ ${signal} received.
|
|
|
1206
1238
|
device: options.device,
|
|
1207
1239
|
build: options.build,
|
|
1208
1240
|
metadata: options.metadata,
|
|
1241
|
+
retryFailed: options.retry === false ? false : void 0,
|
|
1209
1242
|
platform: options.platform,
|
|
1210
1243
|
app: options.app
|
|
1211
1244
|
});
|
|
@@ -1243,7 +1276,7 @@ ${chalk6.dim("Test:")} ${testId}
|
|
|
1243
1276
|
response.build_warning ? { ...result, build_warning: response.build_warning } : result,
|
|
1244
1277
|
options
|
|
1245
1278
|
);
|
|
1246
|
-
process.exit(
|
|
1279
|
+
process.exit(runExitCode(result));
|
|
1247
1280
|
} catch (err) {
|
|
1248
1281
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
1249
1282
|
outputRunError(err, options, {
|
|
@@ -1383,11 +1416,11 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
1383
1416
|
import chalk10 from "chalk";
|
|
1384
1417
|
import { existsSync, readFileSync } from "fs";
|
|
1385
1418
|
async function readStdin() {
|
|
1386
|
-
return new Promise((
|
|
1419
|
+
return new Promise((resolve2, reject) => {
|
|
1387
1420
|
let data = "";
|
|
1388
1421
|
const timeout = setTimeout(() => {
|
|
1389
1422
|
process.stdin.removeAllListeners();
|
|
1390
|
-
|
|
1423
|
+
resolve2(data);
|
|
1391
1424
|
}, 5e3);
|
|
1392
1425
|
process.stdin.setEncoding("utf-8");
|
|
1393
1426
|
process.stdin.on("data", (chunk) => {
|
|
@@ -1395,7 +1428,7 @@ async function readStdin() {
|
|
|
1395
1428
|
});
|
|
1396
1429
|
process.stdin.on("end", () => {
|
|
1397
1430
|
clearTimeout(timeout);
|
|
1398
|
-
|
|
1431
|
+
resolve2(data);
|
|
1399
1432
|
});
|
|
1400
1433
|
process.stdin.on("error", (err) => {
|
|
1401
1434
|
clearTimeout(timeout);
|
|
@@ -1439,14 +1472,16 @@ function assertHasSteps(steps) {
|
|
|
1439
1472
|
async function resolveAppId(client, appRef, platform) {
|
|
1440
1473
|
if (appRef.includes(".")) {
|
|
1441
1474
|
if (!platform) {
|
|
1442
|
-
throw new
|
|
1475
|
+
throw new UsageError(
|
|
1443
1476
|
"When using a package name for --app, you must also specify --platform (ios, android)."
|
|
1444
1477
|
);
|
|
1445
1478
|
}
|
|
1446
1479
|
const apps = await client.listApps();
|
|
1447
1480
|
const match = apps.find((a) => a.package_name === appRef && a.platform === platform);
|
|
1448
1481
|
if (!match) {
|
|
1449
|
-
throw new
|
|
1482
|
+
throw new UsageError(
|
|
1483
|
+
`No app found with package name "${appRef}" and platform "${platform}".`
|
|
1484
|
+
);
|
|
1450
1485
|
}
|
|
1451
1486
|
return match.public_id;
|
|
1452
1487
|
}
|
|
@@ -1470,7 +1505,8 @@ async function createTest(options) {
|
|
|
1470
1505
|
steps,
|
|
1471
1506
|
context: options.context,
|
|
1472
1507
|
mode: "ai",
|
|
1473
|
-
if_exists: options.ifExists
|
|
1508
|
+
if_exists: options.ifExists,
|
|
1509
|
+
group_ids: options.group?.length ? options.group : void 0
|
|
1474
1510
|
});
|
|
1475
1511
|
spinner.succeed(
|
|
1476
1512
|
result.outcome === "updated" ? `Test updated: ${result.name}` : result.outcome === "unchanged" ? `Test already exists, unchanged: ${result.name}` : `Test created: ${result.name}`
|
|
@@ -1483,6 +1519,7 @@ async function createTest(options) {
|
|
|
1483
1519
|
name: result.name,
|
|
1484
1520
|
app_id: appPublicId || null,
|
|
1485
1521
|
outcome: result.outcome ?? null,
|
|
1522
|
+
groups: result.groups ?? [],
|
|
1486
1523
|
url: testCaseUrl(baseUrl, result.public_id)
|
|
1487
1524
|
},
|
|
1488
1525
|
null,
|
|
@@ -1498,6 +1535,9 @@ async function createTest(options) {
|
|
|
1498
1535
|
if (result.platform) {
|
|
1499
1536
|
console.log(` ${chalk10.dim(`Platform: ${result.platform}`)}`);
|
|
1500
1537
|
}
|
|
1538
|
+
if (result.groups?.length) {
|
|
1539
|
+
console.log(` ${chalk10.dim(`Groups: ${result.groups.map((g) => g.name).join(", ")}`)}`);
|
|
1540
|
+
}
|
|
1501
1541
|
console.log(` ${chalk10.dim(`Steps: ${steps.length}`)}`);
|
|
1502
1542
|
console.log(chalk10.dim(` ${testCaseUrl(baseUrl, result.public_id)}
|
|
1503
1543
|
`));
|
|
@@ -1511,6 +1551,153 @@ async function createTest(options) {
|
|
|
1511
1551
|
}
|
|
1512
1552
|
}
|
|
1513
1553
|
|
|
1554
|
+
// src/commands/import-maestro.ts
|
|
1555
|
+
import chalk11 from "chalk";
|
|
1556
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
1557
|
+
import { basename, extname, join, relative, resolve, sep } from "path";
|
|
1558
|
+
import { zipSync } from "fflate";
|
|
1559
|
+
var MAX_FOLDER_FLOWS = 200;
|
|
1560
|
+
var MAX_FLOW_BYTES = 256 * 1024;
|
|
1561
|
+
var MAX_TOTAL_BYTES = 5 * 1024 * 1024;
|
|
1562
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
1563
|
+
function readMaestroUpload(path) {
|
|
1564
|
+
if (!existsSync2(path)) throw new UsageError(`Not found: ${path}`);
|
|
1565
|
+
if (statSync(path).isFile()) {
|
|
1566
|
+
const ext = extname(path).toLowerCase();
|
|
1567
|
+
if (![".yaml", ".yml", ".zip"].includes(ext)) {
|
|
1568
|
+
throw new UsageError(
|
|
1569
|
+
"Pass a Maestro flow (.yaml or .yml), a .zip of your flows folder, or the folder itself."
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const size = statSync(path).size;
|
|
1573
|
+
const cap = ext === ".zip" ? MAX_TOTAL_BYTES : MAX_FLOW_BYTES;
|
|
1574
|
+
if (size > cap) {
|
|
1575
|
+
throw new UsageError(`${path} is larger than ${cap / 1024} KB.`);
|
|
1576
|
+
}
|
|
1577
|
+
return { name: basename(path), bytes: readFileSync2(path) };
|
|
1578
|
+
}
|
|
1579
|
+
const root = resolve(path);
|
|
1580
|
+
const entries = {};
|
|
1581
|
+
let count = 0;
|
|
1582
|
+
let total = 0;
|
|
1583
|
+
const walk = (dir) => {
|
|
1584
|
+
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
1585
|
+
if (ent.isSymbolicLink()) continue;
|
|
1586
|
+
const full = join(dir, ent.name);
|
|
1587
|
+
if (ent.isDirectory()) {
|
|
1588
|
+
if (!SKIP_DIRS.has(ent.name)) walk(full);
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (!ent.isFile() || !/\.ya?ml$/i.test(ent.name)) continue;
|
|
1592
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
1593
|
+
const bytes2 = readFileSync2(full);
|
|
1594
|
+
count += 1;
|
|
1595
|
+
total += bytes2.length;
|
|
1596
|
+
if (count > MAX_FOLDER_FLOWS) {
|
|
1597
|
+
throw new UsageError(`${path} holds more than ${MAX_FOLDER_FLOWS} flow files.`);
|
|
1598
|
+
}
|
|
1599
|
+
if (bytes2.length > MAX_FLOW_BYTES) {
|
|
1600
|
+
throw new UsageError(`${rel} is larger than ${MAX_FLOW_BYTES / 1024} KB.`);
|
|
1601
|
+
}
|
|
1602
|
+
if (total > MAX_TOTAL_BYTES) {
|
|
1603
|
+
throw new UsageError(`The flows in ${path} add up to more than 5 MB.`);
|
|
1604
|
+
}
|
|
1605
|
+
entries[rel] = bytes2;
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
walk(root);
|
|
1609
|
+
if (count === 0) throw new UsageError(`No .yaml or .yml flows found in ${path}.`);
|
|
1610
|
+
const bytes = zipSync(entries);
|
|
1611
|
+
if (bytes.length > MAX_TOTAL_BYTES) {
|
|
1612
|
+
throw new UsageError(`The zipped flows in ${path} are larger than 5 MB.`);
|
|
1613
|
+
}
|
|
1614
|
+
return { name: `${basename(root) || "flows"}.zip`, bytes };
|
|
1615
|
+
}
|
|
1616
|
+
var FLAG_LABEL = {
|
|
1617
|
+
rewrite: "needs rewrite",
|
|
1618
|
+
check: "check",
|
|
1619
|
+
dropped: "not imported"
|
|
1620
|
+
};
|
|
1621
|
+
var START_STATE_LABEL = {
|
|
1622
|
+
reinstall: "Fresh install",
|
|
1623
|
+
clear_data: "Clean app data",
|
|
1624
|
+
relaunch: "Restart the app",
|
|
1625
|
+
none: "Where the last test stopped"
|
|
1626
|
+
};
|
|
1627
|
+
function flagLine(f) {
|
|
1628
|
+
const where = [f.file, f.where, f.command].filter(Boolean).join(" \xB7 ");
|
|
1629
|
+
const label = f.severity === "rewrite" ? chalk11.red(FLAG_LABEL.rewrite) : FLAG_LABEL[f.severity];
|
|
1630
|
+
return ` ${label} ${chalk11.dim(where)} ${f.message}`;
|
|
1631
|
+
}
|
|
1632
|
+
function formatImport(result, baseUrl) {
|
|
1633
|
+
const lines = [];
|
|
1634
|
+
const tests = result.tests.filter((t) => t.importable);
|
|
1635
|
+
if (result.created === null) {
|
|
1636
|
+
lines.push(
|
|
1637
|
+
`Preview: ${tests.length} test(s) and ${result.setups.length} setup(s) would be created. Nothing was saved; run again without --dry-run to import.`
|
|
1638
|
+
);
|
|
1639
|
+
} else {
|
|
1640
|
+
lines.push(`Created ${result.created} test(s), setups included.`);
|
|
1641
|
+
}
|
|
1642
|
+
for (const f of result.flags) lines.push(flagLine(f));
|
|
1643
|
+
for (const t of [...result.tests, ...result.setups]) {
|
|
1644
|
+
const meta = [`${t.steps.length} steps`];
|
|
1645
|
+
if (t.setup_keys.length) meta.push(`before this test: ${t.setup_keys.join(", ")}`);
|
|
1646
|
+
if (t.start_state) meta.push(`start: ${START_STATE_LABEL[t.start_state] ?? t.start_state}`);
|
|
1647
|
+
const head = t.importable ? t.name : chalk11.red(`${t.name} (not imported)`);
|
|
1648
|
+
lines.push("", ` ${head} ${chalk11.dim(`${t.key} \xB7 ${meta.join(" \xB7 ")}`)}`);
|
|
1649
|
+
if (t.created_id) lines.push(` ${chalk11.dim(testCaseUrl(baseUrl, t.created_id))}`);
|
|
1650
|
+
for (const f of t.flags) lines.push(flagLine(f));
|
|
1651
|
+
}
|
|
1652
|
+
if (result.test_data.length) {
|
|
1653
|
+
lines.push("", "These tests use test data:");
|
|
1654
|
+
for (const d of result.test_data) {
|
|
1655
|
+
const note = d.status === "needs_value" || d.status === "skipped" || d.suggested_value === null ? "add the value under App notes & data" : d.status === "kept" ? "already set, kept" : d.status === "created" ? "added from the flow" : d.secret ? "stored as a secret" : `flow value: ${d.suggested_value}`;
|
|
1656
|
+
lines.push(` ${d.key} ${chalk11.dim(note)}`);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return lines;
|
|
1660
|
+
}
|
|
1661
|
+
async function importMaestro(path, options) {
|
|
1662
|
+
let upload;
|
|
1663
|
+
try {
|
|
1664
|
+
if (!options.app) throw new UsageError("Pass the app to import into: --app <app id>.");
|
|
1665
|
+
upload = readMaestroUpload(path);
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
if (options.output === "json") printJsonError(err);
|
|
1668
|
+
throw err;
|
|
1669
|
+
}
|
|
1670
|
+
const { token, baseUrl } = resolveAuth(options);
|
|
1671
|
+
const client = new ApiClient(baseUrl, token);
|
|
1672
|
+
try {
|
|
1673
|
+
const appId = await resolveAppId(client, options.app, options.platform);
|
|
1674
|
+
const spinner = createSpinner(options.dryRun ? "Reading flows..." : "Importing flows...", {
|
|
1675
|
+
silent: options.output === "json"
|
|
1676
|
+
});
|
|
1677
|
+
spinner.start();
|
|
1678
|
+
let result;
|
|
1679
|
+
try {
|
|
1680
|
+
result = await client.importMaestro(upload, {
|
|
1681
|
+
appId,
|
|
1682
|
+
group: options.group,
|
|
1683
|
+
dryRun: options.dryRun === true
|
|
1684
|
+
});
|
|
1685
|
+
} finally {
|
|
1686
|
+
spinner.stop();
|
|
1687
|
+
}
|
|
1688
|
+
if (options.output === "json") {
|
|
1689
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
for (const line of formatImport(result, baseUrl)) console.log(line);
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
if (options.output === "json") printJsonError(err);
|
|
1695
|
+
console.error(chalk11.red(err instanceof Error ? err.message : "Import failed"));
|
|
1696
|
+
const rejected = err instanceof UsageError || err instanceof NotFoundError || err instanceof ApiStatusError && err.status >= 400 && err.status < 500 ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR;
|
|
1697
|
+
process.exit(errorExitCode(err, rejected));
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1514
1701
|
// src/commands/login.ts
|
|
1515
1702
|
import { randomUUID } from "crypto";
|
|
1516
1703
|
import open from "open";
|
|
@@ -1588,7 +1775,7 @@ async function logout() {
|
|
|
1588
1775
|
}
|
|
1589
1776
|
|
|
1590
1777
|
// src/commands/dev-status.ts
|
|
1591
|
-
import
|
|
1778
|
+
import chalk12 from "chalk";
|
|
1592
1779
|
function buildLine(info) {
|
|
1593
1780
|
if (!info || typeof info.app_build_id !== "number") return null;
|
|
1594
1781
|
const version = info.app_version ? ` version ${info.app_version}${info.app_build_number ? ` (${info.app_build_number})` : ""}` : "";
|
|
@@ -1649,7 +1836,7 @@ async function devStatus(opts) {
|
|
|
1649
1836
|
console.error(` url: ${state.page_url}`);
|
|
1650
1837
|
console.error(` started: ${state.started_at}`);
|
|
1651
1838
|
}
|
|
1652
|
-
if (metro) console.error(
|
|
1839
|
+
if (metro) console.error(chalk12.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
|
|
1653
1840
|
process.exit(0);
|
|
1654
1841
|
}
|
|
1655
1842
|
var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "queued", "starting", "active"]);
|
|
@@ -1674,7 +1861,7 @@ async function reportOrphan(api, state, opts) {
|
|
|
1674
1861
|
);
|
|
1675
1862
|
} else if (live) {
|
|
1676
1863
|
console.error(
|
|
1677
|
-
|
|
1864
|
+
chalk12.yellow(
|
|
1678
1865
|
`\u26A0 The dev process (pid ${state.pid}) has exited, but session ${state.session_id} is still ${status}.`
|
|
1679
1866
|
)
|
|
1680
1867
|
);
|
|
@@ -1722,12 +1909,12 @@ async function devStop(opts) {
|
|
|
1722
1909
|
}
|
|
1723
1910
|
|
|
1724
1911
|
// src/commands/dev-reload.ts
|
|
1725
|
-
import
|
|
1912
|
+
import chalk13 from "chalk";
|
|
1726
1913
|
function fail(opts, code, message, exit) {
|
|
1727
1914
|
if (opts.json) {
|
|
1728
1915
|
console.log(JSON.stringify({ reloaded: false, code, message }));
|
|
1729
1916
|
} else {
|
|
1730
|
-
console.error(
|
|
1917
|
+
console.error(chalk13.red(`error: ${message}`));
|
|
1731
1918
|
}
|
|
1732
1919
|
process.exit(exit);
|
|
1733
1920
|
}
|
|
@@ -1780,7 +1967,7 @@ async function devReload(opts) {
|
|
|
1780
1967
|
api,
|
|
1781
1968
|
// Build output and the "reloaded in" line are human output: stderr, so
|
|
1782
1969
|
// stdout stays reserved for the --json result.
|
|
1783
|
-
log: (line) => console.error(
|
|
1970
|
+
log: (line) => console.error(chalk13.dim(line))
|
|
1784
1971
|
});
|
|
1785
1972
|
const started = Date.now();
|
|
1786
1973
|
try {
|
|
@@ -1802,10 +1989,10 @@ async function devReload(opts) {
|
|
|
1802
1989
|
}
|
|
1803
1990
|
|
|
1804
1991
|
// src/commands/builds.ts
|
|
1805
|
-
import
|
|
1992
|
+
import chalk15 from "chalk";
|
|
1806
1993
|
|
|
1807
1994
|
// src/lib/formatters/builds.ts
|
|
1808
|
-
import
|
|
1995
|
+
import chalk14 from "chalk";
|
|
1809
1996
|
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1810
1997
|
var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
1811
1998
|
function formatCostCell(costMicro, status) {
|
|
@@ -1813,13 +2000,13 @@ function formatCostCell(costMicro, status) {
|
|
|
1813
2000
|
return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
|
|
1814
2001
|
}
|
|
1815
2002
|
var STATUS_COLOR = {
|
|
1816
|
-
succeeded:
|
|
1817
|
-
failed:
|
|
1818
|
-
timed_out:
|
|
1819
|
-
infra_failed:
|
|
1820
|
-
canceled:
|
|
1821
|
-
running:
|
|
1822
|
-
queued:
|
|
2003
|
+
succeeded: chalk14.green,
|
|
2004
|
+
failed: chalk14.red,
|
|
2005
|
+
timed_out: chalk14.red,
|
|
2006
|
+
infra_failed: chalk14.red,
|
|
2007
|
+
canceled: chalk14.dim,
|
|
2008
|
+
running: chalk14.yellow,
|
|
2009
|
+
queued: chalk14.dim
|
|
1823
2010
|
};
|
|
1824
2011
|
function formatBuildsHuman(builds2) {
|
|
1825
2012
|
if (builds2.length === 0) {
|
|
@@ -1835,13 +2022,13 @@ function formatBuildsHuman(builds2) {
|
|
|
1835
2022
|
]);
|
|
1836
2023
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1837
2024
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1838
|
-
const
|
|
1839
|
-
const lines = [
|
|
2025
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
2026
|
+
const lines = [chalk14.bold(join5(padCells(HEADERS)))];
|
|
1840
2027
|
for (const row of rows) {
|
|
1841
2028
|
const padded = padCells(row);
|
|
1842
2029
|
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
1843
2030
|
padded[2] = colorFn(row[2]) + padded[2].slice(row[2].length);
|
|
1844
|
-
lines.push(
|
|
2031
|
+
lines.push(join5(padded));
|
|
1845
2032
|
}
|
|
1846
2033
|
return lines.join("\n") + "\n";
|
|
1847
2034
|
}
|
|
@@ -1895,7 +2082,7 @@ async function buildsLogs(id, opts, deps) {
|
|
|
1895
2082
|
console.log(JSON.stringify(logs));
|
|
1896
2083
|
} else {
|
|
1897
2084
|
if (logs.content) process.stdout.write(logs.content);
|
|
1898
|
-
console.error(
|
|
2085
|
+
console.error(chalk15.dim(`status: ${logs.status}`));
|
|
1899
2086
|
}
|
|
1900
2087
|
process.exitCode = 0;
|
|
1901
2088
|
return;
|
|
@@ -1992,7 +2179,7 @@ async function buildsDownload(id, opts, deps) {
|
|
|
1992
2179
|
if (opts.json) {
|
|
1993
2180
|
console.log(JSON.stringify(result));
|
|
1994
2181
|
} else {
|
|
1995
|
-
console.log(
|
|
2182
|
+
console.log(chalk15.green(`\u2713 saved ${result.path}`));
|
|
1996
2183
|
}
|
|
1997
2184
|
process.exitCode = 0;
|
|
1998
2185
|
} catch (err) {
|
|
@@ -2009,7 +2196,7 @@ async function buildsCancel(id, opts, deps) {
|
|
|
2009
2196
|
if (opts.json) {
|
|
2010
2197
|
console.log(JSON.stringify(build));
|
|
2011
2198
|
} else {
|
|
2012
|
-
console.log(`${
|
|
2199
|
+
console.log(`${chalk15.cyan(build.id)} ${build.status}`);
|
|
2013
2200
|
}
|
|
2014
2201
|
process.exitCode = 0;
|
|
2015
2202
|
} catch (err) {
|
|
@@ -2026,7 +2213,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
2026
2213
|
if (opts.json) {
|
|
2027
2214
|
console.log(JSON.stringify(result));
|
|
2028
2215
|
} else {
|
|
2029
|
-
console.log(
|
|
2216
|
+
console.log(chalk15.green(`\u2713 deleted artifact for ${id}`));
|
|
2030
2217
|
}
|
|
2031
2218
|
process.exitCode = 0;
|
|
2032
2219
|
} catch (err) {
|
|
@@ -2037,11 +2224,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
2037
2224
|
function printError(opts, err) {
|
|
2038
2225
|
const message = err instanceof Error ? err.message : String(err);
|
|
2039
2226
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2040
|
-
console.error(
|
|
2227
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2041
2228
|
}
|
|
2042
2229
|
|
|
2043
2230
|
// src/commands/submit.ts
|
|
2044
|
-
import
|
|
2231
|
+
import chalk16 from "chalk";
|
|
2045
2232
|
function defaultDeps2(client) {
|
|
2046
2233
|
return {
|
|
2047
2234
|
createSubmission: client.createSubmission.bind(client),
|
|
@@ -2065,7 +2252,7 @@ function submitConfigFor(cwd, platform) {
|
|
|
2065
2252
|
cfg = loadDevConfig(cwd);
|
|
2066
2253
|
} catch (err) {
|
|
2067
2254
|
console.error(
|
|
2068
|
-
|
|
2255
|
+
chalk16.yellow(
|
|
2069
2256
|
`warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
|
|
2070
2257
|
)
|
|
2071
2258
|
);
|
|
@@ -2213,12 +2400,12 @@ async function submitCommand(opts, deps) {
|
|
|
2213
2400
|
return;
|
|
2214
2401
|
}
|
|
2215
2402
|
if (!opts.json) {
|
|
2216
|
-
console.error(` ${
|
|
2403
|
+
console.error(` ${chalk16.cyan(sub.id)} ${chalk16.dim(`[${sub.platform}]`)}`);
|
|
2217
2404
|
}
|
|
2218
2405
|
const final = await followSubmission(sub, opts, d);
|
|
2219
2406
|
if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
|
|
2220
2407
|
console.error(
|
|
2221
|
-
|
|
2408
|
+
chalk16.dim("release status: draft \u2014 activate it from the Play Console when ready")
|
|
2222
2409
|
);
|
|
2223
2410
|
}
|
|
2224
2411
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
@@ -2249,11 +2436,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
|
2249
2436
|
});
|
|
2250
2437
|
emit(opts.json, { event: "preflight", ...result });
|
|
2251
2438
|
if (!opts.json) {
|
|
2252
|
-
const mark = result.ready ?
|
|
2439
|
+
const mark = result.ready ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
2253
2440
|
console.error(`${mark} ${result.message}`);
|
|
2254
2441
|
if (result.ready) {
|
|
2255
2442
|
console.error(
|
|
2256
|
-
|
|
2443
|
+
chalk16.dim(
|
|
2257
2444
|
` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
|
|
2258
2445
|
)
|
|
2259
2446
|
);
|
|
@@ -2287,7 +2474,7 @@ async function linkedIdentifier(platform, opts, deps) {
|
|
|
2287
2474
|
}
|
|
2288
2475
|
if (!app.package_name) return void 0;
|
|
2289
2476
|
if (!opts.json) {
|
|
2290
|
-
console.error(
|
|
2477
|
+
console.error(chalk16.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
|
|
2291
2478
|
}
|
|
2292
2479
|
return app.package_name;
|
|
2293
2480
|
}
|
|
@@ -2328,14 +2515,14 @@ async function followSubmission(created, opts, deps) {
|
|
|
2328
2515
|
if (opts.json) {
|
|
2329
2516
|
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
2330
2517
|
} else {
|
|
2331
|
-
console.error(
|
|
2518
|
+
console.error(chalk16.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
2332
2519
|
}
|
|
2333
2520
|
} else if (log.status === "running" && !printedRunning) {
|
|
2334
2521
|
printedRunning = true;
|
|
2335
2522
|
if (opts.json) {
|
|
2336
2523
|
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
2337
2524
|
} else {
|
|
2338
|
-
console.error(`${
|
|
2525
|
+
console.error(`${chalk16.cyan("\u25B6")} submitting`);
|
|
2339
2526
|
}
|
|
2340
2527
|
}
|
|
2341
2528
|
if (log.content) {
|
|
@@ -2377,7 +2564,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2377
2564
|
});
|
|
2378
2565
|
if (!opts.json) {
|
|
2379
2566
|
const ok = final.status === "succeeded";
|
|
2380
|
-
const mark = ok ?
|
|
2567
|
+
const mark = ok ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
2381
2568
|
const duration = formatDuration(final.started_at, final.finished_at);
|
|
2382
2569
|
let line = `${mark} ${final.status}`;
|
|
2383
2570
|
if (duration) line += ` (${duration})`;
|
|
@@ -2389,11 +2576,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
|
|
|
2389
2576
|
function printError2(opts, err) {
|
|
2390
2577
|
const message = err instanceof Error ? err.message : String(err);
|
|
2391
2578
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2392
|
-
console.error(
|
|
2579
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2393
2580
|
}
|
|
2394
2581
|
|
|
2395
2582
|
// src/commands/versions.ts
|
|
2396
|
-
import
|
|
2583
|
+
import chalk17 from "chalk";
|
|
2397
2584
|
function defaultDeps3(client) {
|
|
2398
2585
|
return {
|
|
2399
2586
|
listAppVersions: client.listAppVersions.bind(client),
|
|
@@ -2443,7 +2630,7 @@ async function versionsSet(buildNumber, opts, deps) {
|
|
|
2443
2630
|
console.log(JSON.stringify(version));
|
|
2444
2631
|
} else {
|
|
2445
2632
|
console.log(
|
|
2446
|
-
|
|
2633
|
+
chalk17.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk17.dim(
|
|
2447
2634
|
" \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
|
|
2448
2635
|
)
|
|
2449
2636
|
);
|
|
@@ -2462,19 +2649,19 @@ function formatVersionsHuman(versions2) {
|
|
|
2462
2649
|
const rows = versions2.map((v) => [v.platform, String(v.build_number), v.updated_at ?? ""]);
|
|
2463
2650
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2464
2651
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2465
|
-
const
|
|
2466
|
-
const lines = [
|
|
2467
|
-
for (const row of rows) lines.push(
|
|
2652
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
2653
|
+
const lines = [chalk17.bold(join5(padCells(HEADERS2)))];
|
|
2654
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
2468
2655
|
return lines.join("\n") + "\n";
|
|
2469
2656
|
}
|
|
2470
2657
|
function printError3(opts, err) {
|
|
2471
2658
|
const message = err instanceof Error ? err.message : String(err);
|
|
2472
2659
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2473
|
-
console.error(
|
|
2660
|
+
console.error(chalk17.red(`error: ${message}`));
|
|
2474
2661
|
}
|
|
2475
2662
|
|
|
2476
2663
|
// src/commands/whoami.ts
|
|
2477
|
-
import
|
|
2664
|
+
import chalk18 from "chalk";
|
|
2478
2665
|
function defaultDeps4(opts) {
|
|
2479
2666
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2480
2667
|
const client = new ApiClient(baseUrl, token);
|
|
@@ -2505,33 +2692,33 @@ async function whoAmI(opts, deps) {
|
|
|
2505
2692
|
process.exitCode = 0;
|
|
2506
2693
|
return;
|
|
2507
2694
|
}
|
|
2508
|
-
console.log(`${
|
|
2695
|
+
console.log(`${chalk18.bold("Logged in as")} ${me.email}`);
|
|
2509
2696
|
if (me.memberships.length === 1) {
|
|
2510
|
-
console.log(`${
|
|
2697
|
+
console.log(`${chalk18.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
2511
2698
|
} else if (me.memberships.length > 1) {
|
|
2512
|
-
console.log(
|
|
2699
|
+
console.log(chalk18.bold("Organizations"));
|
|
2513
2700
|
for (const m of me.memberships) {
|
|
2514
2701
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
2515
2702
|
}
|
|
2516
2703
|
}
|
|
2517
|
-
console.log(`${
|
|
2518
|
-
console.log(`${
|
|
2519
|
-
console.log(`${
|
|
2704
|
+
console.log(`${chalk18.bold("Member role")} ${me.role ?? "\u2014"}`);
|
|
2705
|
+
console.log(`${chalk18.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
|
|
2706
|
+
console.log(`${chalk18.bold("Effective role")} ${effective ?? "\u2014"}`);
|
|
2520
2707
|
if (me.token_access) {
|
|
2521
2708
|
console.log(
|
|
2522
|
-
`${
|
|
2709
|
+
`${chalk18.bold("Access")} ${me.token_access === "ci" ? "CI only (upload builds, run tests, read results)" : "Full access"}`
|
|
2523
2710
|
);
|
|
2524
|
-
console.log(`${
|
|
2711
|
+
console.log(`${chalk18.bold("Expires")} ${describeExpiry(me.token_expires_at)}`);
|
|
2525
2712
|
}
|
|
2526
2713
|
if (me.is_superadmin) {
|
|
2527
|
-
console.log(
|
|
2714
|
+
console.log(chalk18.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
2528
2715
|
}
|
|
2529
2716
|
if (me.role === "admin" && me.token_role && me.token_role !== "admin" && me.token_access !== "ci") {
|
|
2530
2717
|
console.log(
|
|
2531
|
-
|
|
2718
|
+
chalk18.yellow(
|
|
2532
2719
|
`
|
|
2533
|
-
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 ${
|
|
2534
|
-
Create an admin API token in the dashboard and use ${
|
|
2720
|
+
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 ${chalk18.bold("vibeview env set")}) will fail with a permissions error.
|
|
2721
|
+
Create an admin API token in the dashboard and use ${chalk18.bold("--token")} or ${chalk18.bold("VIBEVIEW_API_TOKEN")}.`
|
|
2535
2722
|
)
|
|
2536
2723
|
);
|
|
2537
2724
|
}
|
|
@@ -2541,15 +2728,15 @@ Create an admin API token in the dashboard and use ${chalk17.bold("--token")} or
|
|
|
2541
2728
|
if (opts.json) {
|
|
2542
2729
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2543
2730
|
}
|
|
2544
|
-
console.error(
|
|
2731
|
+
console.error(chalk18.red(`error: ${message}`));
|
|
2545
2732
|
process.exitCode = errorExitCode(err);
|
|
2546
2733
|
}
|
|
2547
2734
|
}
|
|
2548
2735
|
|
|
2549
2736
|
// src/commands/env.ts
|
|
2550
2737
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
2551
|
-
import { basename } from "path";
|
|
2552
|
-
import
|
|
2738
|
+
import { basename as basename2 } from "path";
|
|
2739
|
+
import chalk19 from "chalk";
|
|
2553
2740
|
import { password } from "@inquirer/prompts";
|
|
2554
2741
|
|
|
2555
2742
|
// src/lib/gitignore.ts
|
|
@@ -2605,7 +2792,7 @@ function defaultDeps5(client) {
|
|
|
2605
2792
|
isIgnored: (path) => isGitIgnored(path),
|
|
2606
2793
|
writeFile: writeSecureFile,
|
|
2607
2794
|
readFile: (path) => readFileAsync(path),
|
|
2608
|
-
warn: (message) => console.error(
|
|
2795
|
+
warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
|
|
2609
2796
|
};
|
|
2610
2797
|
}
|
|
2611
2798
|
async function envList(opts, deps) {
|
|
@@ -2636,9 +2823,9 @@ function formatEnvVarsHuman(variables) {
|
|
|
2636
2823
|
]);
|
|
2637
2824
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2638
2825
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2639
|
-
const
|
|
2640
|
-
const lines = [
|
|
2641
|
-
for (const row of rows) lines.push(
|
|
2826
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
2827
|
+
const lines = [chalk19.bold(join5(padCells(HEADERS3)))];
|
|
2828
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
2642
2829
|
return lines.join("\n") + "\n";
|
|
2643
2830
|
}
|
|
2644
2831
|
async function envSet(name, value, opts, deps) {
|
|
@@ -2655,7 +2842,7 @@ async function envSet(name, value, opts, deps) {
|
|
|
2655
2842
|
const visibility = resolveVisibility(opts);
|
|
2656
2843
|
if (opts.file) {
|
|
2657
2844
|
const content = await d.readFile(opts.file);
|
|
2658
|
-
const filename =
|
|
2845
|
+
const filename = basename2(opts.file);
|
|
2659
2846
|
const result2 = await d.setFileVar({ name, content, filename, visibility, app: opts.app });
|
|
2660
2847
|
printSetResult(opts, result2);
|
|
2661
2848
|
process.exitCode = 0;
|
|
@@ -2687,7 +2874,7 @@ function printSetResult(opts, result) {
|
|
|
2687
2874
|
if (opts.json) {
|
|
2688
2875
|
console.log(JSON.stringify(result));
|
|
2689
2876
|
} else {
|
|
2690
|
-
console.log(
|
|
2877
|
+
console.log(chalk19.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
2691
2878
|
}
|
|
2692
2879
|
}
|
|
2693
2880
|
async function envRm(name, opts, deps) {
|
|
@@ -2697,7 +2884,7 @@ async function envRm(name, opts, deps) {
|
|
|
2697
2884
|
if (opts.json) {
|
|
2698
2885
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2699
2886
|
} else {
|
|
2700
|
-
console.log(
|
|
2887
|
+
console.log(chalk19.green(`\u2713 removed ${name}`));
|
|
2701
2888
|
}
|
|
2702
2889
|
process.exitCode = 0;
|
|
2703
2890
|
} catch (err) {
|
|
@@ -2729,7 +2916,7 @@ async function envPull(opts, deps) {
|
|
|
2729
2916
|
if (opts.json) {
|
|
2730
2917
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
2731
2918
|
} else {
|
|
2732
|
-
console.log(
|
|
2919
|
+
console.log(chalk19.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
2733
2920
|
}
|
|
2734
2921
|
process.exitCode = 0;
|
|
2735
2922
|
} catch (err) {
|
|
@@ -2768,7 +2955,7 @@ function escapeEnvValue(value) {
|
|
|
2768
2955
|
function printError4(opts, err) {
|
|
2769
2956
|
const message = err instanceof Error ? err.message : String(err);
|
|
2770
2957
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2771
|
-
console.error(
|
|
2958
|
+
console.error(chalk19.red(`error: ${message}`));
|
|
2772
2959
|
}
|
|
2773
2960
|
|
|
2774
2961
|
// src/commands/credentials.ts
|
|
@@ -2779,8 +2966,8 @@ import {
|
|
|
2779
2966
|
writeFile as writeFileAsync
|
|
2780
2967
|
} from "fs/promises";
|
|
2781
2968
|
import { mkdirSync } from "fs";
|
|
2782
|
-
import { basename as
|
|
2783
|
-
import
|
|
2969
|
+
import { basename as basename3, extname as extname2, join as join2 } from "path";
|
|
2970
|
+
import chalk20 from "chalk";
|
|
2784
2971
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
2785
2972
|
function clientFor2(opts) {
|
|
2786
2973
|
const { token, baseUrl } = resolveAuth(opts);
|
|
@@ -2853,7 +3040,7 @@ function defaultDeps6(client) {
|
|
|
2853
3040
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
2854
3041
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
2855
3042
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
2856
|
-
warn: (message) => console.error(
|
|
3043
|
+
warn: (message) => console.error(chalk20.yellow(`warning: ${message}`))
|
|
2857
3044
|
};
|
|
2858
3045
|
}
|
|
2859
3046
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -2883,10 +3070,10 @@ async function credentialsAdd(opts, deps) {
|
|
|
2883
3070
|
"a password is required: run interactively to be prompted (passwords are never accepted as flags)."
|
|
2884
3071
|
);
|
|
2885
3072
|
}
|
|
2886
|
-
const name = opts.name ??
|
|
3073
|
+
const name = opts.name ?? basename3(opts.file, extname2(opts.file));
|
|
2887
3074
|
try {
|
|
2888
3075
|
const file = await d.readFile(opts.file);
|
|
2889
|
-
const filename =
|
|
3076
|
+
const filename = basename3(opts.file);
|
|
2890
3077
|
let body;
|
|
2891
3078
|
if (platform === "android") {
|
|
2892
3079
|
const storePassword = await d.prompt({ message: "Keystore (store) password", hidden: true });
|
|
@@ -2915,14 +3102,14 @@ async function credentialsAdd(opts, deps) {
|
|
|
2915
3102
|
file,
|
|
2916
3103
|
filename,
|
|
2917
3104
|
profile,
|
|
2918
|
-
profileFilename:
|
|
3105
|
+
profileFilename: basename3(opts.profile)
|
|
2919
3106
|
};
|
|
2920
3107
|
}
|
|
2921
3108
|
const result = await d.addCredential(body);
|
|
2922
3109
|
if (opts.json) {
|
|
2923
3110
|
console.log(JSON.stringify(result));
|
|
2924
3111
|
} else {
|
|
2925
|
-
console.log(
|
|
3112
|
+
console.log(chalk20.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
2926
3113
|
}
|
|
2927
3114
|
process.exitCode = 0;
|
|
2928
3115
|
} catch (err) {
|
|
@@ -2948,7 +3135,7 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2948
3135
|
const outDir = opts.out ?? process.cwd();
|
|
2949
3136
|
try {
|
|
2950
3137
|
d.mkdir(outDir);
|
|
2951
|
-
const expectedPath =
|
|
3138
|
+
const expectedPath = join2(outDir, `${opts.name}.keystore`);
|
|
2952
3139
|
if (await d.fileExists(expectedPath)) {
|
|
2953
3140
|
throw new Error(
|
|
2954
3141
|
`${expectedPath} already exists \u2014 refusing to overwrite it. Move it away or pass a different --out/--name.`
|
|
@@ -2963,7 +3150,7 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2963
3150
|
// charge, so a stale CLI can never accidentally opt out of it.
|
|
2964
3151
|
...opts.force ? { overwrite: true } : {}
|
|
2965
3152
|
});
|
|
2966
|
-
const savedTo =
|
|
3153
|
+
const savedTo = join2(outDir, basename3(result.filename));
|
|
2967
3154
|
if (opts.json) {
|
|
2968
3155
|
console.log(JSON.stringify({ ...result, saved_to: savedTo }));
|
|
2969
3156
|
}
|
|
@@ -2980,20 +3167,20 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2980
3167
|
}
|
|
2981
3168
|
if (opts.json) {
|
|
2982
3169
|
} else {
|
|
2983
|
-
console.log(
|
|
3170
|
+
console.log(chalk20.green(`\u2713 generated ${result.name} (android)`));
|
|
2984
3171
|
console.log("");
|
|
2985
|
-
console.log(` Keystore saved to: ${
|
|
2986
|
-
console.log(` Key alias: ${
|
|
2987
|
-
console.log(` Store password: ${
|
|
2988
|
-
console.log(` Key password: ${
|
|
3172
|
+
console.log(` Keystore saved to: ${chalk20.bold(savedTo)}`);
|
|
3173
|
+
console.log(` Key alias: ${chalk20.bold(result.key_alias)}`);
|
|
3174
|
+
console.log(` Store password: ${chalk20.bold(result.store_password)}`);
|
|
3175
|
+
console.log(` Key password: ${chalk20.bold(result.key_password)}`);
|
|
2989
3176
|
console.log("");
|
|
2990
3177
|
console.log(
|
|
2991
|
-
|
|
3178
|
+
chalk20.yellow(
|
|
2992
3179
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
2993
3180
|
)
|
|
2994
3181
|
);
|
|
2995
3182
|
console.log(
|
|
2996
|
-
|
|
3183
|
+
chalk20.yellow(
|
|
2997
3184
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
2998
3185
|
)
|
|
2999
3186
|
);
|
|
@@ -3019,19 +3206,19 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
3019
3206
|
if (opts.json) {
|
|
3020
3207
|
console.log(JSON.stringify(result));
|
|
3021
3208
|
} else {
|
|
3022
|
-
console.log(
|
|
3209
|
+
console.log(chalk20.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
3023
3210
|
console.log("");
|
|
3024
|
-
console.log(` Scope: ${
|
|
3211
|
+
console.log(` Scope: ${chalk20.bold(result.app_public_id ?? "org-wide")}`);
|
|
3025
3212
|
const expires = formatExpires(result);
|
|
3026
3213
|
if (expires) {
|
|
3027
|
-
console.log(` Expires: ${
|
|
3214
|
+
console.log(` Expires: ${chalk20.bold(expires)}`);
|
|
3028
3215
|
}
|
|
3029
3216
|
console.log("");
|
|
3030
3217
|
console.log(
|
|
3031
3218
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
3032
3219
|
);
|
|
3033
3220
|
console.log(
|
|
3034
|
-
` Run ${
|
|
3221
|
+
` Run ${chalk20.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
3035
3222
|
);
|
|
3036
3223
|
}
|
|
3037
3224
|
process.exitCode = 0;
|
|
@@ -3072,8 +3259,8 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
3072
3259
|
}
|
|
3073
3260
|
throw err;
|
|
3074
3261
|
}
|
|
3075
|
-
const savedTo =
|
|
3076
|
-
const profileSavedTo = material.profile_base64 != null ?
|
|
3262
|
+
const savedTo = join2(outDir, basename3(material.filename));
|
|
3263
|
+
const profileSavedTo = material.profile_base64 != null ? join2(outDir, `${basename3(material.filename, extname2(material.filename))}.mobileprovision`) : null;
|
|
3077
3264
|
if (opts.json) {
|
|
3078
3265
|
console.log(
|
|
3079
3266
|
JSON.stringify({
|
|
@@ -3097,17 +3284,17 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
3097
3284
|
await d.chmod(path, 384);
|
|
3098
3285
|
}
|
|
3099
3286
|
if (!opts.json) {
|
|
3100
|
-
console.log(
|
|
3287
|
+
console.log(chalk20.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
3101
3288
|
console.log("");
|
|
3102
|
-
console.log(` Saved to: ${
|
|
3289
|
+
console.log(` Saved to: ${chalk20.bold(savedTo)}`);
|
|
3103
3290
|
if (profileSavedTo !== null) {
|
|
3104
|
-
console.log(` Profile: ${
|
|
3291
|
+
console.log(` Profile: ${chalk20.bold(profileSavedTo)}`);
|
|
3105
3292
|
}
|
|
3106
3293
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
3107
|
-
console.log(` ${key}: ${
|
|
3294
|
+
console.log(` ${key}: ${chalk20.bold(value)}`);
|
|
3108
3295
|
}
|
|
3109
3296
|
console.log("");
|
|
3110
|
-
console.log(
|
|
3297
|
+
console.log(chalk20.yellow(" This download is recorded in your organization's audit log."));
|
|
3111
3298
|
}
|
|
3112
3299
|
process.exitCode = 0;
|
|
3113
3300
|
} catch (err) {
|
|
@@ -3133,18 +3320,18 @@ async function ascKeyAdd(opts, deps) {
|
|
|
3133
3320
|
keyId: opts.keyId,
|
|
3134
3321
|
issuerId: opts.issuerId,
|
|
3135
3322
|
file,
|
|
3136
|
-
filename:
|
|
3323
|
+
filename: basename3(opts.file)
|
|
3137
3324
|
});
|
|
3138
3325
|
if (opts.json) {
|
|
3139
3326
|
console.log(JSON.stringify(result));
|
|
3140
3327
|
} else {
|
|
3141
|
-
console.log(
|
|
3328
|
+
console.log(chalk20.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
3142
3329
|
console.log("");
|
|
3143
|
-
console.log(` Key ID: ${
|
|
3144
|
-
console.log(` Issuer ID: ${
|
|
3330
|
+
console.log(` Key ID: ${chalk20.bold(result.key_id)}`);
|
|
3331
|
+
console.log(` Issuer ID: ${chalk20.bold(result.issuer_id)}`);
|
|
3145
3332
|
console.log("");
|
|
3146
3333
|
console.log(
|
|
3147
|
-
|
|
3334
|
+
chalk20.yellow(
|
|
3148
3335
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
3149
3336
|
)
|
|
3150
3337
|
);
|
|
@@ -3178,9 +3365,9 @@ function formatAscKeysHuman(keys) {
|
|
|
3178
3365
|
const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, formatUtcTimestamp(k.created_at)]);
|
|
3179
3366
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3180
3367
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3181
|
-
const
|
|
3182
|
-
const lines = [
|
|
3183
|
-
for (const row of rows) lines.push(
|
|
3368
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
3369
|
+
const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
|
|
3370
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
3184
3371
|
return lines.join("\n") + "\n";
|
|
3185
3372
|
}
|
|
3186
3373
|
async function ascKeyRm(name, opts, deps) {
|
|
@@ -3190,7 +3377,7 @@ async function ascKeyRm(name, opts, deps) {
|
|
|
3190
3377
|
if (opts.json) {
|
|
3191
3378
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3192
3379
|
} else {
|
|
3193
|
-
console.log(
|
|
3380
|
+
console.log(chalk20.green(`\u2713 removed App Store Connect key ${name}`));
|
|
3194
3381
|
}
|
|
3195
3382
|
process.exitCode = 0;
|
|
3196
3383
|
} catch (err) {
|
|
@@ -3210,17 +3397,17 @@ async function playKeyAdd(opts, deps) {
|
|
|
3210
3397
|
const result = await d.addPlayKey({
|
|
3211
3398
|
...opts.name !== void 0 ? { name: opts.name } : {},
|
|
3212
3399
|
file,
|
|
3213
|
-
filename:
|
|
3400
|
+
filename: basename3(opts.file)
|
|
3214
3401
|
});
|
|
3215
3402
|
if (opts.json) {
|
|
3216
3403
|
console.log(JSON.stringify(result));
|
|
3217
3404
|
} else {
|
|
3218
|
-
console.log(
|
|
3405
|
+
console.log(chalk20.green(`\u2713 added Google Play key ${result.name}`));
|
|
3219
3406
|
console.log("");
|
|
3220
|
-
console.log(` Service account: ${
|
|
3407
|
+
console.log(` Service account: ${chalk20.bold(result.client_email)}`);
|
|
3221
3408
|
console.log("");
|
|
3222
3409
|
console.log(
|
|
3223
|
-
|
|
3410
|
+
chalk20.yellow(
|
|
3224
3411
|
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
3225
3412
|
)
|
|
3226
3413
|
);
|
|
@@ -3254,9 +3441,9 @@ function formatPlayKeysHuman(keys) {
|
|
|
3254
3441
|
const rows = keys.map((k) => [k.name, k.client_email, formatUtcTimestamp(k.created_at)]);
|
|
3255
3442
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3256
3443
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3257
|
-
const
|
|
3258
|
-
const lines = [
|
|
3259
|
-
for (const row of rows) lines.push(
|
|
3444
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
3445
|
+
const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
|
|
3446
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
3260
3447
|
return lines.join("\n") + "\n";
|
|
3261
3448
|
}
|
|
3262
3449
|
async function playKeyRm(name, opts, deps) {
|
|
@@ -3266,7 +3453,7 @@ async function playKeyRm(name, opts, deps) {
|
|
|
3266
3453
|
if (opts.json) {
|
|
3267
3454
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3268
3455
|
} else {
|
|
3269
|
-
console.log(
|
|
3456
|
+
console.log(chalk20.green(`\u2713 removed Google Play key ${name}`));
|
|
3270
3457
|
}
|
|
3271
3458
|
process.exitCode = 0;
|
|
3272
3459
|
} catch (err) {
|
|
@@ -3334,9 +3521,9 @@ function formatCredentialsHuman(credentials2) {
|
|
|
3334
3521
|
]);
|
|
3335
3522
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3336
3523
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3337
|
-
const
|
|
3338
|
-
const lines = [
|
|
3339
|
-
for (const row of rows) lines.push(
|
|
3524
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
3525
|
+
const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
|
|
3526
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
3340
3527
|
return lines.join("\n") + "\n";
|
|
3341
3528
|
}
|
|
3342
3529
|
async function credentialsRm(name, opts, deps) {
|
|
@@ -3346,7 +3533,7 @@ async function credentialsRm(name, opts, deps) {
|
|
|
3346
3533
|
if (opts.json) {
|
|
3347
3534
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
3348
3535
|
} else {
|
|
3349
|
-
console.log(
|
|
3536
|
+
console.log(chalk20.green(`\u2713 removed ${name}`));
|
|
3350
3537
|
}
|
|
3351
3538
|
process.exitCode = 0;
|
|
3352
3539
|
} catch (err) {
|
|
@@ -3358,16 +3545,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3358
3545
|
const message = err instanceof Error ? err.message : String(err);
|
|
3359
3546
|
if (opts.json) {
|
|
3360
3547
|
console.log(JSON.stringify({ event: "error", message }));
|
|
3361
|
-
console.error(
|
|
3548
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3362
3549
|
return;
|
|
3363
3550
|
}
|
|
3364
|
-
console.error(
|
|
3551
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3365
3552
|
console.error(
|
|
3366
|
-
|
|
3553
|
+
chalk20.red(
|
|
3367
3554
|
`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.`
|
|
3368
3555
|
)
|
|
3369
3556
|
);
|
|
3370
|
-
console.error(
|
|
3557
|
+
console.error(chalk20.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
3371
3558
|
console.error("");
|
|
3372
3559
|
console.error(` Key alias: ${result.key_alias}`);
|
|
3373
3560
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -3381,11 +3568,11 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
3381
3568
|
function printError5(opts, err) {
|
|
3382
3569
|
const message = err instanceof Error ? err.message : String(err);
|
|
3383
3570
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3384
|
-
console.error(
|
|
3571
|
+
console.error(chalk20.red(`error: ${message}`));
|
|
3385
3572
|
}
|
|
3386
3573
|
|
|
3387
3574
|
// src/commands/webhooks.ts
|
|
3388
|
-
import
|
|
3575
|
+
import chalk21 from "chalk";
|
|
3389
3576
|
function clientFor3(opts) {
|
|
3390
3577
|
const { token, baseUrl } = resolveAuth(opts);
|
|
3391
3578
|
return new ApiClient(baseUrl, token);
|
|
@@ -3416,14 +3603,14 @@ async function webhooksAdd(opts, deps) {
|
|
|
3416
3603
|
if (opts.json) {
|
|
3417
3604
|
console.log(JSON.stringify(result));
|
|
3418
3605
|
} else {
|
|
3419
|
-
console.log(
|
|
3606
|
+
console.log(chalk21.green(`\u2713 added ${result.id} ${result.url}`));
|
|
3420
3607
|
console.log("");
|
|
3421
|
-
console.log(` Signing secret: ${
|
|
3608
|
+
console.log(` Signing secret: ${chalk21.bold(result.secret)}`);
|
|
3422
3609
|
if (opts.secret !== void 0) {
|
|
3423
|
-
console.log(
|
|
3610
|
+
console.log(chalk21.yellow(" (This is the secret you provided.)"));
|
|
3424
3611
|
}
|
|
3425
3612
|
console.log(
|
|
3426
|
-
|
|
3613
|
+
chalk21.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
3427
3614
|
);
|
|
3428
3615
|
}
|
|
3429
3616
|
process.exitCode = 0;
|
|
@@ -3453,9 +3640,9 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
3453
3640
|
]);
|
|
3454
3641
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3455
3642
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
3456
|
-
const
|
|
3457
|
-
const lines = [
|
|
3458
|
-
for (const row of rows) lines.push(
|
|
3643
|
+
const join5 = (cells) => cells.join(" ").trimEnd();
|
|
3644
|
+
const lines = [chalk21.bold(join5(padCells(HEADERS3)))];
|
|
3645
|
+
for (const row of rows) lines.push(join5(padCells(row)));
|
|
3459
3646
|
return lines.join("\n") + "\n";
|
|
3460
3647
|
}
|
|
3461
3648
|
async function webhooksList(opts, deps) {
|
|
@@ -3480,7 +3667,7 @@ async function webhooksRm(id, opts, deps) {
|
|
|
3480
3667
|
if (opts.json) {
|
|
3481
3668
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
3482
3669
|
} else {
|
|
3483
|
-
console.log(
|
|
3670
|
+
console.log(chalk21.green(`\u2713 removed ${id}`));
|
|
3484
3671
|
}
|
|
3485
3672
|
process.exitCode = 0;
|
|
3486
3673
|
} catch (err) {
|
|
@@ -3513,13 +3700,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3513
3700
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
3514
3701
|
if (ld.status === "succeeded") {
|
|
3515
3702
|
if (!opts.json) {
|
|
3516
|
-
console.log(
|
|
3703
|
+
console.log(chalk21.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
3517
3704
|
}
|
|
3518
3705
|
process.exitCode = 0;
|
|
3519
3706
|
} else {
|
|
3520
3707
|
if (!opts.json) {
|
|
3521
3708
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
3522
|
-
console.error(
|
|
3709
|
+
console.error(chalk21.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
|
|
3523
3710
|
}
|
|
3524
3711
|
process.exitCode = 1;
|
|
3525
3712
|
}
|
|
@@ -3528,7 +3715,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3528
3715
|
}
|
|
3529
3716
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
3530
3717
|
console.error(
|
|
3531
|
-
|
|
3718
|
+
chalk21.yellow(
|
|
3532
3719
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
3533
3720
|
)
|
|
3534
3721
|
);
|
|
@@ -3541,7 +3728,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
3541
3728
|
function printError6(opts, err) {
|
|
3542
3729
|
const message = err instanceof Error ? err.message : String(err);
|
|
3543
3730
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
3544
|
-
console.error(
|
|
3731
|
+
console.error(chalk21.red(`error: ${message}`));
|
|
3545
3732
|
}
|
|
3546
3733
|
|
|
3547
3734
|
// src/commands/list-devices.ts
|
|
@@ -3582,7 +3769,7 @@ async function listDevicesCommand(options) {
|
|
|
3582
3769
|
}
|
|
3583
3770
|
|
|
3584
3771
|
// src/lib/metadata.ts
|
|
3585
|
-
import { readFileSync as
|
|
3772
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3586
3773
|
var MAX_METADATA_BYTES = 16384;
|
|
3587
3774
|
var MAX_METADATA_DEPTH = 4;
|
|
3588
3775
|
function measureDepth(value, current = 1) {
|
|
@@ -3621,7 +3808,7 @@ function parseMetadata(raw) {
|
|
|
3621
3808
|
function readMetadataFile(path) {
|
|
3622
3809
|
let raw;
|
|
3623
3810
|
try {
|
|
3624
|
-
raw =
|
|
3811
|
+
raw = readFileSync3(path, "utf8");
|
|
3625
3812
|
} catch (e) {
|
|
3626
3813
|
throw new Error(`failed to read --metadata-file ${path}: ${e.message}`);
|
|
3627
3814
|
}
|
|
@@ -3633,27 +3820,27 @@ function readMetadataFile(path) {
|
|
|
3633
3820
|
}
|
|
3634
3821
|
|
|
3635
3822
|
// src/commands/agent-setup.ts
|
|
3636
|
-
import { copyFileSync, existsSync as
|
|
3823
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
3637
3824
|
import { homedir } from "os";
|
|
3638
|
-
import { dirname, join as
|
|
3825
|
+
import { dirname, join as join3 } from "path";
|
|
3639
3826
|
import { fileURLToPath } from "url";
|
|
3640
3827
|
import { spawnSync } from "child_process";
|
|
3641
3828
|
import { confirm } from "@inquirer/prompts";
|
|
3642
|
-
import
|
|
3829
|
+
import chalk22 from "chalk";
|
|
3643
3830
|
function resolveShippedSkillPath(fromDir) {
|
|
3644
|
-
for (const rel of ["..",
|
|
3645
|
-
const candidate =
|
|
3646
|
-
if (
|
|
3831
|
+
for (const rel of ["..", join3("..", "..")]) {
|
|
3832
|
+
const candidate = join3(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
3833
|
+
if (existsSync3(candidate)) return candidate;
|
|
3647
3834
|
}
|
|
3648
3835
|
return null;
|
|
3649
3836
|
}
|
|
3650
3837
|
function skillInstallDir(project, cwd, home) {
|
|
3651
3838
|
const base = project ? cwd : home;
|
|
3652
|
-
return
|
|
3839
|
+
return join3(base, ".claude", "skills", "vibeview-agent");
|
|
3653
3840
|
}
|
|
3654
3841
|
function installSkill(shippedPath, targetDir) {
|
|
3655
|
-
const target =
|
|
3656
|
-
const existed =
|
|
3842
|
+
const target = join3(targetDir, "SKILL.md");
|
|
3843
|
+
const existed = existsSync3(target);
|
|
3657
3844
|
mkdirSync2(targetDir, { recursive: true });
|
|
3658
3845
|
copyFileSync(shippedPath, target);
|
|
3659
3846
|
return existed ? "updated" : "installed";
|
|
@@ -3669,23 +3856,23 @@ function registerMcp() {
|
|
|
3669
3856
|
encoding: "utf8"
|
|
3670
3857
|
});
|
|
3671
3858
|
if (res.status === 0) {
|
|
3672
|
-
console.log(
|
|
3859
|
+
console.log(chalk22.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
3673
3860
|
return;
|
|
3674
3861
|
}
|
|
3675
3862
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
3676
3863
|
if (out.includes("already exists")) {
|
|
3677
|
-
console.log(
|
|
3864
|
+
console.log(chalk22.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
3678
3865
|
return;
|
|
3679
3866
|
}
|
|
3680
|
-
console.error(
|
|
3681
|
-
console.error(
|
|
3867
|
+
console.error(chalk22.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
3868
|
+
console.error(chalk22.yellow(` register it manually: ${MCP_ADD_HINT}`));
|
|
3682
3869
|
}
|
|
3683
3870
|
async function agentSetup(opts) {
|
|
3684
3871
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3685
3872
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
3686
3873
|
if (!shipped) {
|
|
3687
3874
|
console.error(
|
|
3688
|
-
|
|
3875
|
+
chalk22.red(
|
|
3689
3876
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
3690
3877
|
)
|
|
3691
3878
|
);
|
|
@@ -3694,9 +3881,9 @@ async function agentSetup(opts) {
|
|
|
3694
3881
|
}
|
|
3695
3882
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
3696
3883
|
const result = installSkill(shipped, targetDir);
|
|
3697
|
-
console.log(
|
|
3884
|
+
console.log(chalk22.green(`\u2713 vibeview-agent skill ${result}: ${join3(targetDir, "SKILL.md")}`));
|
|
3698
3885
|
console.log(
|
|
3699
|
-
|
|
3886
|
+
chalk22.dim(
|
|
3700
3887
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
3701
3888
|
)
|
|
3702
3889
|
);
|
|
@@ -3704,7 +3891,7 @@ async function agentSetup(opts) {
|
|
|
3704
3891
|
if (!claudeCliAvailable()) {
|
|
3705
3892
|
if (opts.mcp === true) {
|
|
3706
3893
|
console.error(
|
|
3707
|
-
|
|
3894
|
+
chalk22.yellow(`\u26A0 claude CLI not found \u2014 install Claude Code, then run: ${MCP_ADD_HINT}`)
|
|
3708
3895
|
);
|
|
3709
3896
|
}
|
|
3710
3897
|
} else if (opts.mcp === true || process.stdin.isTTY && await confirm({
|
|
@@ -3716,7 +3903,7 @@ async function agentSetup(opts) {
|
|
|
3716
3903
|
}
|
|
3717
3904
|
console.log(
|
|
3718
3905
|
`
|
|
3719
|
-
Suggested line for your project's ${
|
|
3906
|
+
Suggested line for your project's ${chalk22.bold("CLAUDE.md")} (or ${chalk22.bold(
|
|
3720
3907
|
"AGENTS.md"
|
|
3721
3908
|
)} for other agents):
|
|
3722
3909
|
`
|
|
@@ -3724,17 +3911,17 @@ Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bol
|
|
|
3724
3911
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
3725
3912
|
`);
|
|
3726
3913
|
console.log(
|
|
3727
|
-
|
|
3914
|
+
chalk22.dim(
|
|
3728
3915
|
"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"
|
|
3729
3916
|
)
|
|
3730
3917
|
);
|
|
3731
3918
|
}
|
|
3732
3919
|
|
|
3733
3920
|
// src/lib/update-check.ts
|
|
3734
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
3921
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
3735
3922
|
import { homedir as homedir2 } from "os";
|
|
3736
|
-
import { dirname as dirname2, join as
|
|
3737
|
-
var CACHE_FILE =
|
|
3923
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
3924
|
+
var CACHE_FILE = join4(homedir2(), ".vibeview", "update-check.json");
|
|
3738
3925
|
var REGISTRY_URL = "https://registry.npmjs.org/vibeview/latest";
|
|
3739
3926
|
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3740
3927
|
function isNewerVersion(a, b) {
|
|
@@ -3765,7 +3952,7 @@ function updateNotice(current, latest) {
|
|
|
3765
3952
|
}
|
|
3766
3953
|
function readCache(file) {
|
|
3767
3954
|
try {
|
|
3768
|
-
const parsed = JSON.parse(
|
|
3955
|
+
const parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
3769
3956
|
if (typeof parsed?.checked_at === "number" && typeof parsed?.latest === "string") {
|
|
3770
3957
|
return parsed;
|
|
3771
3958
|
}
|
|
@@ -3848,11 +4035,14 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
|
|
|
3848
4035
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3849
4036
|
).option(
|
|
3850
4037
|
"--platform <platform>",
|
|
3851
|
-
"Run the suite on another platform (ios, android, tvos, androidtv): runs your app
|
|
4038
|
+
"Run the suite on another platform (ios, android, tvos, androidtv): runs your linked app on that platform, else the app with the same bundle id / package"
|
|
3852
4039
|
).option(
|
|
3853
4040
|
"--app <app-id>",
|
|
3854
4041
|
"Run the suite against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3855
|
-
).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)").
|
|
4042
|
+
).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)").option(
|
|
4043
|
+
"--no-retry",
|
|
4044
|
+
"Do not retry a failed test once (CI retries a failed test once by default)"
|
|
4045
|
+
).action((suiteId, opts) => {
|
|
3856
4046
|
if (opts.device && opts.deviceCategory) {
|
|
3857
4047
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3858
4048
|
process.exit(2);
|
|
@@ -3880,7 +4070,7 @@ program.command("run-suites").description("Run all suites matching a tag filter"
|
|
|
3880
4070
|
[]
|
|
3881
4071
|
).option("--match <mode>", "any (default) or all", "any").option(
|
|
3882
4072
|
"--platform <platform>",
|
|
3883
|
-
"Filter matched suites to one platform: ios, android, tvos, androidtv (default: no filter)"
|
|
4073
|
+
"Filter matched suites to one platform: ios, android, tvos, androidtv, roku (default: no filter)"
|
|
3884
4074
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--output <format>", "Output format: human, json, junit", "human").option("--verbose", "Show per-suite progress", false).option("--timeout <seconds>", "Per-suite timeout, capped at 2 hours total", "1800").option("--commit-sha <sha>", "Git commit SHA propagated to every SuiteRun").option("--model <model>", "AI model").option("--provider <provider>", "AI provider").option("--mode <mode>", "Execution mode: hybrid, ai, replay").option(
|
|
3885
4075
|
"--device-category <category>",
|
|
3886
4076
|
"Device category: phone, tablet, tv (default: tv for a TV platform, else phone)",
|
|
@@ -3888,13 +4078,16 @@ program.command("run-suites").description("Run all suites matching a tag filter"
|
|
|
3888
4078
|
).option(
|
|
3889
4079
|
"--device <device_id>",
|
|
3890
4080
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3891
|
-
).option("--build <id>", "AppBuild id (validated against every matched suite)").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)").
|
|
4081
|
+
).option("--build <id>", "AppBuild id (validated against every matched suite)").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)").option(
|
|
4082
|
+
"--no-retry",
|
|
4083
|
+
"Do not retry a failed test once (CI retries a failed test once by default)"
|
|
4084
|
+
).action((opts) => {
|
|
3892
4085
|
if (opts.device && opts.deviceCategory) {
|
|
3893
4086
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3894
4087
|
process.exit(2);
|
|
3895
4088
|
}
|
|
3896
4089
|
try {
|
|
3897
|
-
|
|
4090
|
+
parseSuitePlatformFilter(opts.platform);
|
|
3898
4091
|
} catch (err) {
|
|
3899
4092
|
process.stderr.write(`error: ${err.message}
|
|
3900
4093
|
`);
|
|
@@ -3917,11 +4110,14 @@ program.command("run-test <test-id>").description("Run a single test case").opti
|
|
|
3917
4110
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3918
4111
|
).option(
|
|
3919
4112
|
"--platform <platform>",
|
|
3920
|
-
"Run the test on another platform (ios, android, tvos, androidtv): runs your app
|
|
4113
|
+
"Run the test on another platform (ios, android, tvos, androidtv): runs your linked app on that platform, else the app with the same bundle id / package"
|
|
3921
4114
|
).option(
|
|
3922
4115
|
"--app <app-id>",
|
|
3923
4116
|
"Run the test against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3924
|
-
).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)").
|
|
4117
|
+
).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)").option(
|
|
4118
|
+
"--no-retry",
|
|
4119
|
+
"Do not retry a failed test once (CI retries a failed test once by default)"
|
|
4120
|
+
).action((testId, opts) => {
|
|
3925
4121
|
if (opts.device && opts.deviceCategory) {
|
|
3926
4122
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3927
4123
|
process.exit(2);
|
|
@@ -3969,12 +4165,18 @@ program.command("list-devices").description("List devices registered with the pl
|
|
|
3969
4165
|
program.command("upload-app <file>").description(
|
|
3970
4166
|
"Upload an app build (zipped simulator .app for iOS/tvOS, .apk for Android/Android TV, or a Roku channel .zip)"
|
|
3971
4167
|
).option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--name <name>", "App name (auto-detected if not provided)").option("--note <text>", "Note stored on the uploaded build (shown in the dashboard)").action((file, opts) => uploadApp(file, opts));
|
|
3972
|
-
program.command("create-test [name]").description("Create
|
|
4168
|
+
program.command("create-test [name]").description("Create an AI test case, optionally in one or more groups").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--app <app>", "App ID (public_id) or package name (requires --platform)").option("--platform <platform>", "Platform (ios, android) when using package name for --app").option("--steps <json>", "Test steps as inline JSON array").option("--steps-file <path>", "Path to JSON file containing test steps").option("--context <text>", "Additional context for AI test execution").option("--test-name <name>", "Test case name (alternative to positional arg)").option(
|
|
4169
|
+
"--group <id...>",
|
|
4170
|
+
"Group (suite) ID to add the test to; repeat or list several. Same app as --app"
|
|
4171
|
+
).addOption(
|
|
3973
4172
|
new Option(
|
|
3974
4173
|
"--if-exists <mode>",
|
|
3975
|
-
"When a
|
|
4174
|
+
"When a test with this name already exists for the app (outside any group, or in one of the --group groups): fail, update it, or create a new one alongside it. Default: reuse an identical test, refuse a different one"
|
|
3976
4175
|
).choices(["fail", "update", "new"])
|
|
3977
4176
|
).action((name, opts) => createTest({ ...opts, testName: name || opts.testName || "CLI Test" }));
|
|
4177
|
+
program.command("import-maestro <path>").description(
|
|
4178
|
+
"Import Maestro flows as tests: a flow file, a .zip of your flows folder, or the folder itself"
|
|
4179
|
+
).option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--app <app>", "App ID (public_id) or package name (requires --platform)").option("--platform <platform>", "Platform (ios, android) when using package name for --app").option("--group <group>", "Put the imported tests in this group (name or id)").option("--dry-run", "Show what would be imported and every flagged command; save nothing", false).action((path, opts) => importMaestro(path, opts));
|
|
3978
4180
|
program.command("login").description("Log in to VibeView (interactive)").option("--base-url <url>", "VibeView server URL (defaults to https://vibeview.io)").action((opts) => login(opts));
|
|
3979
4181
|
program.command("logout").description("Revoke the saved login token and remove it from ~/.vibeview/config.json").action(() => logout());
|
|
3980
4182
|
program.command("dev").description(
|
|
@@ -4147,7 +4349,7 @@ program.command("dev-reload").description(
|
|
|
4147
4349
|
program.command("mcp").description(
|
|
4148
4350
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
4149
4351
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
4150
|
-
const { runMcpServer } = await import("./server-
|
|
4352
|
+
const { runMcpServer } = await import("./server-FMU3HQWF.js");
|
|
4151
4353
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
4152
4354
|
});
|
|
4153
4355
|
program.command("agent-setup").description(
|