vibeview 0.18.0 → 0.19.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 +40 -28
- package/dist/{chunk-BJEDH672.js → chunk-FG2J2BGS.js} +61 -3
- package/dist/{chunk-MGP3UR7P.js → chunk-T6FS4ELD.js} +40 -6
- package/dist/cli.js +418 -121
- package/dist/{server-TJQKNXDR.js → server-GMC6F545.js} +3 -3
- package/dist/{upload-app-Y7MXT2PI.js → upload-app-4PE5BEP7.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
clearDevState,
|
|
7
7
|
dev,
|
|
8
8
|
fetchFinalSummary,
|
|
9
|
+
formatDuration,
|
|
9
10
|
killOwnedMetro,
|
|
10
11
|
newTransitionState,
|
|
11
12
|
printOutcomeLine,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
readDevState,
|
|
14
15
|
readMetroState,
|
|
15
16
|
runAgentVerb
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-T6FS4ELD.js";
|
|
17
18
|
import {
|
|
18
19
|
ApiClient,
|
|
19
20
|
EXIT_INFRA_ERROR,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
resolveAuth,
|
|
27
28
|
saveConfig,
|
|
28
29
|
uploadApp
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-FG2J2BGS.js";
|
|
30
31
|
|
|
31
32
|
// src/cli.ts
|
|
32
33
|
import { createRequire } from "module";
|
|
@@ -1210,7 +1211,7 @@ async function buildsList(opts, deps) {
|
|
|
1210
1211
|
}
|
|
1211
1212
|
}
|
|
1212
1213
|
try {
|
|
1213
|
-
const { builds: builds2 } = await d.listCloudBuilds(limit);
|
|
1214
|
+
const { builds: builds2 } = await d.listCloudBuilds(limit !== void 0 ? { limit } : void 0);
|
|
1214
1215
|
if (opts.json) {
|
|
1215
1216
|
console.log(JSON.stringify(builds2, null, 2));
|
|
1216
1217
|
} else {
|
|
@@ -1378,9 +1379,192 @@ function printError(opts, err) {
|
|
|
1378
1379
|
console.error(chalk11.red(`error: ${message}`));
|
|
1379
1380
|
}
|
|
1380
1381
|
|
|
1381
|
-
// src/commands/
|
|
1382
|
+
// src/commands/submit.ts
|
|
1382
1383
|
import chalk12 from "chalk";
|
|
1383
|
-
function defaultDeps2(
|
|
1384
|
+
function defaultDeps2(client) {
|
|
1385
|
+
return {
|
|
1386
|
+
createSubmission: client.createSubmission.bind(client),
|
|
1387
|
+
getSubmission: client.getSubmission.bind(client),
|
|
1388
|
+
getSubmissionLog: client.getSubmissionLog.bind(client),
|
|
1389
|
+
listCloudBuilds: client.listCloudBuilds.bind(client)
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
var POLL_INTERVAL_MS2 = 2e3;
|
|
1393
|
+
var SUBMIT_PLATFORMS = ["ios", "tvos", "android", "androidtv"];
|
|
1394
|
+
function isSubmitPlatform(p) {
|
|
1395
|
+
return SUBMIT_PLATFORMS.includes(p);
|
|
1396
|
+
}
|
|
1397
|
+
var PLAY_TRACKS = ["internal", "alpha", "beta", "production"];
|
|
1398
|
+
var SUBMISSION_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
|
|
1399
|
+
var MAX_CONSECUTIVE_POLL_FAILURES2 = 5;
|
|
1400
|
+
function emit(json, evt) {
|
|
1401
|
+
if (json) console.log(JSON.stringify(evt));
|
|
1402
|
+
}
|
|
1403
|
+
async function submitCommand(opts, deps) {
|
|
1404
|
+
if (!opts.platform || !isSubmitPlatform(opts.platform)) {
|
|
1405
|
+
throw new UsageError(
|
|
1406
|
+
`--platform is required and must be one of ${SUBMIT_PLATFORMS.join(", ")}`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
const platform = opts.platform;
|
|
1410
|
+
const androidFamily = platform === "android" || platform === "androidtv";
|
|
1411
|
+
if (opts.build && opts.latest) {
|
|
1412
|
+
throw new UsageError("pass either --build <id> or --latest, not both");
|
|
1413
|
+
}
|
|
1414
|
+
if (!opts.build && !opts.latest) {
|
|
1415
|
+
throw new UsageError(
|
|
1416
|
+
"pass --build <id> (see `vibeview builds list`) or --latest (the newest succeeded production build for this platform)"
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
if (!androidFamily && opts.track !== void 0) {
|
|
1420
|
+
throw new UsageError(`--track only applies to android/androidtv (Play) \u2014 not ${platform}`);
|
|
1421
|
+
}
|
|
1422
|
+
if (!androidFamily && opts.package !== void 0) {
|
|
1423
|
+
throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${platform}`);
|
|
1424
|
+
}
|
|
1425
|
+
if (androidFamily && opts.ascKey !== void 0) {
|
|
1426
|
+
throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \u2014 not ${platform}`);
|
|
1427
|
+
}
|
|
1428
|
+
if (opts.track !== void 0 && !PLAY_TRACKS.includes(opts.track)) {
|
|
1429
|
+
throw new UsageError(`--track must be one of ${PLAY_TRACKS.join(", ")} \u2014 got "${opts.track}"`);
|
|
1430
|
+
}
|
|
1431
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1432
|
+
const client = new ApiClient(baseUrl, token);
|
|
1433
|
+
const d = deps ?? defaultDeps2(client);
|
|
1434
|
+
try {
|
|
1435
|
+
const buildId = opts.build ?? await resolveLatestBuild(platform, d);
|
|
1436
|
+
const sub = await d.createSubmission({
|
|
1437
|
+
build: buildId,
|
|
1438
|
+
platform,
|
|
1439
|
+
...opts.track !== void 0 ? { track: opts.track } : {},
|
|
1440
|
+
...opts.package !== void 0 ? { package: opts.package } : {},
|
|
1441
|
+
...opts.ascKey !== void 0 ? { asc_key: opts.ascKey } : {}
|
|
1442
|
+
});
|
|
1443
|
+
emit(opts.json, {
|
|
1444
|
+
event: "submission_created",
|
|
1445
|
+
id: sub.id,
|
|
1446
|
+
build_id: sub.build_id,
|
|
1447
|
+
platform: sub.platform
|
|
1448
|
+
});
|
|
1449
|
+
if (opts.wait === false) {
|
|
1450
|
+
if (!opts.json) console.log(sub.id);
|
|
1451
|
+
process.exitCode = 0;
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (!opts.json) {
|
|
1455
|
+
console.error(` ${chalk12.cyan(sub.id)} ${chalk12.dim(`[${sub.platform}]`)}`);
|
|
1456
|
+
}
|
|
1457
|
+
const final = await followSubmission(sub, opts, d);
|
|
1458
|
+
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
1459
|
+
} catch (err) {
|
|
1460
|
+
printError2(opts, err);
|
|
1461
|
+
process.exitCode = 1;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
async function resolveLatestBuild(platform, deps) {
|
|
1465
|
+
const { builds: builds2 } = await deps.listCloudBuilds({
|
|
1466
|
+
platform,
|
|
1467
|
+
status: "succeeded",
|
|
1468
|
+
distribution: "production",
|
|
1469
|
+
limit: 200
|
|
1470
|
+
});
|
|
1471
|
+
const match = builds2.find(
|
|
1472
|
+
(b) => b.platform === platform && b.distribution === "production" && b.status === "succeeded" && b.has_artifact
|
|
1473
|
+
);
|
|
1474
|
+
if (!match) {
|
|
1475
|
+
throw new Error(
|
|
1476
|
+
`no submittable ${platform} build found \u2014 --latest needs a succeeded production build whose artifact is still stored. Run \`vibeview build --cloud --production --platform ${platform}\` first, or pass --build <id> (see \`vibeview builds list\`).`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
if (match.platform !== platform) {
|
|
1480
|
+
throw new Error(
|
|
1481
|
+
`--latest resolved build ${match.id}, which is ${match.platform}, not ${platform} \u2014 refusing to submit it.`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
return match.id;
|
|
1485
|
+
}
|
|
1486
|
+
async function followSubmission(created, opts, deps) {
|
|
1487
|
+
const pollIntervalMs = deps.pollIntervalMs ?? POLL_INTERVAL_MS2;
|
|
1488
|
+
let offset = 0;
|
|
1489
|
+
let status = created.status;
|
|
1490
|
+
let consecutiveFailures = 0;
|
|
1491
|
+
let printedQueued = false;
|
|
1492
|
+
let printedRunning = false;
|
|
1493
|
+
while (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
|
|
1494
|
+
try {
|
|
1495
|
+
const log = await deps.getSubmissionLog(created.id, offset);
|
|
1496
|
+
if (log.status === "queued" && !printedQueued) {
|
|
1497
|
+
printedQueued = true;
|
|
1498
|
+
if (opts.json) {
|
|
1499
|
+
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
1500
|
+
} else {
|
|
1501
|
+
console.error(chalk12.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
1502
|
+
}
|
|
1503
|
+
} else if (log.status === "running" && !printedRunning) {
|
|
1504
|
+
printedRunning = true;
|
|
1505
|
+
if (opts.json) {
|
|
1506
|
+
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
1507
|
+
} else {
|
|
1508
|
+
console.error(`${chalk12.cyan("\u25B6")} submitting`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (log.content) {
|
|
1512
|
+
if (opts.json) {
|
|
1513
|
+
emit(opts.json, { event: "submission_log", id: created.id, content: log.content });
|
|
1514
|
+
} else {
|
|
1515
|
+
process.stdout.write(log.content);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
offset = log.offset;
|
|
1519
|
+
status = log.status;
|
|
1520
|
+
consecutiveFailures = 0;
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
if (err instanceof NotFoundError) throw err;
|
|
1523
|
+
consecutiveFailures++;
|
|
1524
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES2) {
|
|
1525
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
`submission poll failed ${consecutiveFailures} times in a row, giving up: ${message}
|
|
1528
|
+
The submission continues server-side \u2014 see its outcome on the Builds page in the dashboard (expand build ${created.build_id}'s row \u2192 Submissions).`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
|
|
1533
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
let final;
|
|
1537
|
+
try {
|
|
1538
|
+
final = await deps.getSubmission(created.id);
|
|
1539
|
+
} catch {
|
|
1540
|
+
final = { ...created, status };
|
|
1541
|
+
}
|
|
1542
|
+
emit(opts.json, {
|
|
1543
|
+
event: "submission_finished",
|
|
1544
|
+
id: final.id,
|
|
1545
|
+
status: final.status,
|
|
1546
|
+
error_summary: final.error_summary
|
|
1547
|
+
});
|
|
1548
|
+
if (!opts.json) {
|
|
1549
|
+
const ok = final.status === "succeeded";
|
|
1550
|
+
const mark = ok ? chalk12.green("\u2713") : chalk12.red("\u2717");
|
|
1551
|
+
const duration = formatDuration(final.started_at, final.finished_at);
|
|
1552
|
+
let line = `${mark} ${final.status}`;
|
|
1553
|
+
if (duration) line += ` (${duration})`;
|
|
1554
|
+
if (final.error_summary) line += ` \u2014 ${final.error_summary}`;
|
|
1555
|
+
console.error(line);
|
|
1556
|
+
}
|
|
1557
|
+
return final;
|
|
1558
|
+
}
|
|
1559
|
+
function printError2(opts, err) {
|
|
1560
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1561
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1562
|
+
console.error(chalk12.red(`error: ${message}`));
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/commands/whoami.ts
|
|
1566
|
+
import chalk13 from "chalk";
|
|
1567
|
+
function defaultDeps3(opts) {
|
|
1384
1568
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1385
1569
|
const client = new ApiClient(baseUrl, token);
|
|
1386
1570
|
return { fetchMe: () => client.whoAmI() };
|
|
@@ -1392,7 +1576,7 @@ function effectiveRole(role, tokenRole) {
|
|
|
1392
1576
|
return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
|
|
1393
1577
|
}
|
|
1394
1578
|
async function whoAmI(opts, deps) {
|
|
1395
|
-
const d = deps ??
|
|
1579
|
+
const d = deps ?? defaultDeps3(opts);
|
|
1396
1580
|
try {
|
|
1397
1581
|
const me = await d.fetchMe();
|
|
1398
1582
|
const effective = effectiveRole(me.role, me.token_role);
|
|
@@ -1401,27 +1585,27 @@ async function whoAmI(opts, deps) {
|
|
|
1401
1585
|
process.exitCode = 0;
|
|
1402
1586
|
return;
|
|
1403
1587
|
}
|
|
1404
|
-
console.log(`${
|
|
1588
|
+
console.log(`${chalk13.bold("Logged in as")} ${me.email}`);
|
|
1405
1589
|
if (me.memberships.length === 1) {
|
|
1406
|
-
console.log(`${
|
|
1590
|
+
console.log(`${chalk13.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
1407
1591
|
} else if (me.memberships.length > 1) {
|
|
1408
|
-
console.log(
|
|
1592
|
+
console.log(chalk13.bold("Organizations"));
|
|
1409
1593
|
for (const m of me.memberships) {
|
|
1410
1594
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
1411
1595
|
}
|
|
1412
1596
|
}
|
|
1413
|
-
console.log(`${
|
|
1414
|
-
console.log(`${
|
|
1415
|
-
console.log(`${
|
|
1597
|
+
console.log(`${chalk13.bold("Member role")} ${me.role ?? "\u2014"}`);
|
|
1598
|
+
console.log(`${chalk13.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
|
|
1599
|
+
console.log(`${chalk13.bold("Effective role")} ${effective ?? "\u2014"}`);
|
|
1416
1600
|
if (me.is_superadmin) {
|
|
1417
|
-
console.log(
|
|
1601
|
+
console.log(chalk13.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
1418
1602
|
}
|
|
1419
1603
|
if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
|
|
1420
1604
|
console.log(
|
|
1421
|
-
|
|
1605
|
+
chalk13.yellow(
|
|
1422
1606
|
`
|
|
1423
|
-
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 ${
|
|
1424
|
-
Create an admin API token in the dashboard and use ${
|
|
1607
|
+
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 ${chalk13.bold("vibeview env set")}) will fail with a permissions error.
|
|
1608
|
+
Create an admin API token in the dashboard and use ${chalk13.bold("--token")} or ${chalk13.bold("VIBEVIEW_API_TOKEN")}.`
|
|
1425
1609
|
)
|
|
1426
1610
|
);
|
|
1427
1611
|
}
|
|
@@ -1431,7 +1615,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
|
|
|
1431
1615
|
if (opts.json) {
|
|
1432
1616
|
console.log(JSON.stringify({ event: "error", message }));
|
|
1433
1617
|
}
|
|
1434
|
-
console.error(
|
|
1618
|
+
console.error(chalk13.red(`error: ${message}`));
|
|
1435
1619
|
process.exitCode = 1;
|
|
1436
1620
|
}
|
|
1437
1621
|
}
|
|
@@ -1439,7 +1623,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
|
|
|
1439
1623
|
// src/commands/env.ts
|
|
1440
1624
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
1441
1625
|
import { basename } from "path";
|
|
1442
|
-
import
|
|
1626
|
+
import chalk14 from "chalk";
|
|
1443
1627
|
import { password } from "@inquirer/prompts";
|
|
1444
1628
|
|
|
1445
1629
|
// src/lib/gitignore.ts
|
|
@@ -1466,7 +1650,7 @@ async function writeSecureFile(path, content) {
|
|
|
1466
1650
|
}
|
|
1467
1651
|
await chmod(path, 384);
|
|
1468
1652
|
}
|
|
1469
|
-
function
|
|
1653
|
+
function defaultDeps4(client) {
|
|
1470
1654
|
return {
|
|
1471
1655
|
listVars: (app) => client.listBuildEnvVars(app),
|
|
1472
1656
|
setVar: (body) => client.setBuildEnvVar(body),
|
|
@@ -1495,11 +1679,11 @@ function defaultDeps3(client) {
|
|
|
1495
1679
|
isIgnored: (path) => isGitIgnored(path),
|
|
1496
1680
|
writeFile: writeSecureFile,
|
|
1497
1681
|
readFile: (path) => readFileAsync(path),
|
|
1498
|
-
warn: (message) => console.error(
|
|
1682
|
+
warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
|
|
1499
1683
|
};
|
|
1500
1684
|
}
|
|
1501
1685
|
async function envList(opts, deps) {
|
|
1502
|
-
const d = deps ??
|
|
1686
|
+
const d = deps ?? defaultDeps4(clientFor(opts));
|
|
1503
1687
|
try {
|
|
1504
1688
|
const { variables } = await d.listVars(opts.app);
|
|
1505
1689
|
if (opts.json) {
|
|
@@ -1509,7 +1693,7 @@ async function envList(opts, deps) {
|
|
|
1509
1693
|
}
|
|
1510
1694
|
process.exitCode = 0;
|
|
1511
1695
|
} catch (err) {
|
|
1512
|
-
|
|
1696
|
+
printError3(opts, err);
|
|
1513
1697
|
process.exitCode = 1;
|
|
1514
1698
|
}
|
|
1515
1699
|
}
|
|
@@ -1527,12 +1711,12 @@ function formatEnvVarsHuman(variables) {
|
|
|
1527
1711
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1528
1712
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1529
1713
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1530
|
-
const lines = [
|
|
1714
|
+
const lines = [chalk14.bold(join3(padCells(HEADERS3)))];
|
|
1531
1715
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1532
1716
|
return lines.join("\n") + "\n";
|
|
1533
1717
|
}
|
|
1534
1718
|
async function envSet(name, value, opts, deps) {
|
|
1535
|
-
const d = deps ??
|
|
1719
|
+
const d = deps ?? defaultDeps4(clientFor(opts));
|
|
1536
1720
|
let resolvedValue = value;
|
|
1537
1721
|
if (resolvedValue === void 0 && !opts.file) {
|
|
1538
1722
|
if (opts.json || !d.isTTY) {
|
|
@@ -1558,7 +1742,7 @@ async function envSet(name, value, opts, deps) {
|
|
|
1558
1742
|
printSetResult(opts, result);
|
|
1559
1743
|
process.exitCode = 0;
|
|
1560
1744
|
} catch (err) {
|
|
1561
|
-
|
|
1745
|
+
printError3(opts, err);
|
|
1562
1746
|
process.exitCode = 1;
|
|
1563
1747
|
}
|
|
1564
1748
|
}
|
|
@@ -1577,26 +1761,26 @@ function printSetResult(opts, result) {
|
|
|
1577
1761
|
if (opts.json) {
|
|
1578
1762
|
console.log(JSON.stringify(result));
|
|
1579
1763
|
} else {
|
|
1580
|
-
console.log(
|
|
1764
|
+
console.log(chalk14.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
1581
1765
|
}
|
|
1582
1766
|
}
|
|
1583
1767
|
async function envRm(name, opts, deps) {
|
|
1584
|
-
const d = deps ??
|
|
1768
|
+
const d = deps ?? defaultDeps4(clientFor(opts));
|
|
1585
1769
|
try {
|
|
1586
1770
|
await d.rmVar({ name, app: opts.app });
|
|
1587
1771
|
if (opts.json) {
|
|
1588
1772
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
1589
1773
|
} else {
|
|
1590
|
-
console.log(
|
|
1774
|
+
console.log(chalk14.green(`\u2713 removed ${name}`));
|
|
1591
1775
|
}
|
|
1592
1776
|
process.exitCode = 0;
|
|
1593
1777
|
} catch (err) {
|
|
1594
|
-
|
|
1778
|
+
printError3(opts, err);
|
|
1595
1779
|
process.exitCode = 1;
|
|
1596
1780
|
}
|
|
1597
1781
|
}
|
|
1598
1782
|
async function envPull(opts, deps) {
|
|
1599
|
-
const d = deps ??
|
|
1783
|
+
const d = deps ?? defaultDeps4(clientFor(opts));
|
|
1600
1784
|
const outPath = opts.out ?? ".env";
|
|
1601
1785
|
try {
|
|
1602
1786
|
const { variables } = await d.pullVars(opts.app);
|
|
@@ -1619,11 +1803,11 @@ async function envPull(opts, deps) {
|
|
|
1619
1803
|
if (opts.json) {
|
|
1620
1804
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
1621
1805
|
} else {
|
|
1622
|
-
console.log(
|
|
1806
|
+
console.log(chalk14.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
1623
1807
|
}
|
|
1624
1808
|
process.exitCode = 0;
|
|
1625
1809
|
} catch (err) {
|
|
1626
|
-
|
|
1810
|
+
printError3(opts, err);
|
|
1627
1811
|
process.exitCode = 1;
|
|
1628
1812
|
}
|
|
1629
1813
|
}
|
|
@@ -1655,10 +1839,10 @@ function escapeEnvValue(value) {
|
|
|
1655
1839
|
}
|
|
1656
1840
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1657
1841
|
}
|
|
1658
|
-
function
|
|
1842
|
+
function printError3(opts, err) {
|
|
1659
1843
|
const message = err instanceof Error ? err.message : String(err);
|
|
1660
1844
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1661
|
-
console.error(
|
|
1845
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1662
1846
|
}
|
|
1663
1847
|
|
|
1664
1848
|
// src/commands/credentials.ts
|
|
@@ -1670,13 +1854,13 @@ import {
|
|
|
1670
1854
|
} from "fs/promises";
|
|
1671
1855
|
import { mkdirSync } from "fs";
|
|
1672
1856
|
import { basename as basename2, extname, join } from "path";
|
|
1673
|
-
import
|
|
1857
|
+
import chalk15 from "chalk";
|
|
1674
1858
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
1675
1859
|
function clientFor2(opts) {
|
|
1676
1860
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1677
1861
|
return new ApiClient(baseUrl, token);
|
|
1678
1862
|
}
|
|
1679
|
-
function
|
|
1863
|
+
function defaultDeps5(client) {
|
|
1680
1864
|
return {
|
|
1681
1865
|
addCredential: (body) => client.addSigningCredential({
|
|
1682
1866
|
platform: body.platform,
|
|
@@ -1721,6 +1905,16 @@ function defaultDeps4(client) {
|
|
|
1721
1905
|
}
|
|
1722
1906
|
await client.deleteAscKey(match.id);
|
|
1723
1907
|
},
|
|
1908
|
+
addPlayKey: (body) => client.addPlayKey(body),
|
|
1909
|
+
listPlayKeys: () => client.listPlayKeys(),
|
|
1910
|
+
deletePlayKey: async (name) => {
|
|
1911
|
+
const { keys } = await client.listPlayKeys();
|
|
1912
|
+
const match = keys.find((k) => k.name === name);
|
|
1913
|
+
if (!match) {
|
|
1914
|
+
throw new Error(`No Google Play key named "${name}" found.`);
|
|
1915
|
+
}
|
|
1916
|
+
await client.deletePlayKey(match.id);
|
|
1917
|
+
},
|
|
1724
1918
|
prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
|
|
1725
1919
|
isTTY: Boolean(process.stdin.isTTY),
|
|
1726
1920
|
readFile: (path) => readFileAsync2(path),
|
|
@@ -1733,7 +1927,7 @@ function defaultDeps4(client) {
|
|
|
1733
1927
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
1734
1928
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
1735
1929
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
1736
|
-
warn: (message) => console.error(
|
|
1930
|
+
warn: (message) => console.error(chalk15.yellow(`warning: ${message}`))
|
|
1737
1931
|
};
|
|
1738
1932
|
}
|
|
1739
1933
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -1743,7 +1937,7 @@ function isCredentialPlatform(p) {
|
|
|
1743
1937
|
return CREDENTIAL_PLATFORMS.includes(p);
|
|
1744
1938
|
}
|
|
1745
1939
|
async function credentialsAdd(opts, deps) {
|
|
1746
|
-
const d = deps ??
|
|
1940
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1747
1941
|
if (!opts.platform || !isCredentialPlatform(opts.platform)) {
|
|
1748
1942
|
throw new UsageError('--platform is required and must be "ios", "android", or "tvos"');
|
|
1749
1943
|
}
|
|
@@ -1802,16 +1996,16 @@ async function credentialsAdd(opts, deps) {
|
|
|
1802
1996
|
if (opts.json) {
|
|
1803
1997
|
console.log(JSON.stringify(result));
|
|
1804
1998
|
} else {
|
|
1805
|
-
console.log(
|
|
1999
|
+
console.log(chalk15.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
1806
2000
|
}
|
|
1807
2001
|
process.exitCode = 0;
|
|
1808
2002
|
} catch (err) {
|
|
1809
|
-
|
|
2003
|
+
printError4(opts, err);
|
|
1810
2004
|
process.exitCode = 1;
|
|
1811
2005
|
}
|
|
1812
2006
|
}
|
|
1813
2007
|
async function credentialsGenerate(opts, deps) {
|
|
1814
|
-
const d = deps ??
|
|
2008
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1815
2009
|
if (!opts.platform) {
|
|
1816
2010
|
throw new UsageError('--platform is required and must be "android", "ios", or "tvos"');
|
|
1817
2011
|
}
|
|
@@ -1860,27 +2054,27 @@ async function credentialsGenerate(opts, deps) {
|
|
|
1860
2054
|
}
|
|
1861
2055
|
if (opts.json) {
|
|
1862
2056
|
} else {
|
|
1863
|
-
console.log(
|
|
2057
|
+
console.log(chalk15.green(`\u2713 generated ${result.name} (android)`));
|
|
1864
2058
|
console.log("");
|
|
1865
|
-
console.log(` Keystore saved to: ${
|
|
1866
|
-
console.log(` Key alias: ${
|
|
1867
|
-
console.log(` Store password: ${
|
|
1868
|
-
console.log(` Key password: ${
|
|
2059
|
+
console.log(` Keystore saved to: ${chalk15.bold(savedTo)}`);
|
|
2060
|
+
console.log(` Key alias: ${chalk15.bold(result.key_alias)}`);
|
|
2061
|
+
console.log(` Store password: ${chalk15.bold(result.store_password)}`);
|
|
2062
|
+
console.log(` Key password: ${chalk15.bold(result.key_password)}`);
|
|
1869
2063
|
console.log("");
|
|
1870
2064
|
console.log(
|
|
1871
|
-
|
|
2065
|
+
chalk15.yellow(
|
|
1872
2066
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
1873
2067
|
)
|
|
1874
2068
|
);
|
|
1875
2069
|
console.log(
|
|
1876
|
-
|
|
2070
|
+
chalk15.yellow(
|
|
1877
2071
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
1878
2072
|
)
|
|
1879
2073
|
);
|
|
1880
2074
|
}
|
|
1881
2075
|
process.exitCode = 0;
|
|
1882
2076
|
} catch (err) {
|
|
1883
|
-
|
|
2077
|
+
printError4(opts, err);
|
|
1884
2078
|
process.exitCode = 1;
|
|
1885
2079
|
}
|
|
1886
2080
|
}
|
|
@@ -1899,29 +2093,29 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
1899
2093
|
if (opts.json) {
|
|
1900
2094
|
console.log(JSON.stringify(result));
|
|
1901
2095
|
} else {
|
|
1902
|
-
console.log(
|
|
2096
|
+
console.log(chalk15.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
1903
2097
|
console.log("");
|
|
1904
|
-
console.log(` Scope: ${
|
|
2098
|
+
console.log(` Scope: ${chalk15.bold(result.app_public_id ?? "org-wide")}`);
|
|
1905
2099
|
const expires = formatExpires(result);
|
|
1906
2100
|
if (expires) {
|
|
1907
|
-
console.log(` Expires: ${
|
|
2101
|
+
console.log(` Expires: ${chalk15.bold(expires)}`);
|
|
1908
2102
|
}
|
|
1909
2103
|
console.log("");
|
|
1910
2104
|
console.log(
|
|
1911
2105
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
1912
2106
|
);
|
|
1913
2107
|
console.log(
|
|
1914
|
-
` Run ${
|
|
2108
|
+
` Run ${chalk15.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
1915
2109
|
);
|
|
1916
2110
|
}
|
|
1917
2111
|
process.exitCode = 0;
|
|
1918
2112
|
} catch (err) {
|
|
1919
|
-
|
|
2113
|
+
printError4(opts, err);
|
|
1920
2114
|
process.exitCode = 1;
|
|
1921
2115
|
}
|
|
1922
2116
|
}
|
|
1923
2117
|
async function credentialsDownload(name, opts, deps) {
|
|
1924
|
-
const d = deps ??
|
|
2118
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1925
2119
|
const outDir = opts.out ?? process.cwd();
|
|
1926
2120
|
try {
|
|
1927
2121
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
@@ -1977,26 +2171,26 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
1977
2171
|
await d.chmod(path, 384);
|
|
1978
2172
|
}
|
|
1979
2173
|
if (!opts.json) {
|
|
1980
|
-
console.log(
|
|
2174
|
+
console.log(chalk15.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
1981
2175
|
console.log("");
|
|
1982
|
-
console.log(` Saved to: ${
|
|
2176
|
+
console.log(` Saved to: ${chalk15.bold(savedTo)}`);
|
|
1983
2177
|
if (profileSavedTo !== null) {
|
|
1984
|
-
console.log(` Profile: ${
|
|
2178
|
+
console.log(` Profile: ${chalk15.bold(profileSavedTo)}`);
|
|
1985
2179
|
}
|
|
1986
2180
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
1987
|
-
console.log(` ${key}: ${
|
|
2181
|
+
console.log(` ${key}: ${chalk15.bold(value)}`);
|
|
1988
2182
|
}
|
|
1989
2183
|
console.log("");
|
|
1990
|
-
console.log(
|
|
2184
|
+
console.log(chalk15.yellow(" This download is recorded in your organization's audit log."));
|
|
1991
2185
|
}
|
|
1992
2186
|
process.exitCode = 0;
|
|
1993
2187
|
} catch (err) {
|
|
1994
|
-
|
|
2188
|
+
printError4(opts, err);
|
|
1995
2189
|
process.exitCode = 1;
|
|
1996
2190
|
}
|
|
1997
2191
|
}
|
|
1998
2192
|
async function ascKeyAdd(opts, deps) {
|
|
1999
|
-
const d = deps ??
|
|
2193
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2000
2194
|
if (!opts.keyId) {
|
|
2001
2195
|
throw new UsageError("--key-id is required (the 10-character App Store Connect key id)");
|
|
2002
2196
|
}
|
|
@@ -2018,25 +2212,25 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2018
2212
|
if (opts.json) {
|
|
2019
2213
|
console.log(JSON.stringify(result));
|
|
2020
2214
|
} else {
|
|
2021
|
-
console.log(
|
|
2215
|
+
console.log(chalk15.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2022
2216
|
console.log("");
|
|
2023
|
-
console.log(` Key ID: ${
|
|
2024
|
-
console.log(` Issuer ID: ${
|
|
2217
|
+
console.log(` Key ID: ${chalk15.bold(result.key_id)}`);
|
|
2218
|
+
console.log(` Issuer ID: ${chalk15.bold(result.issuer_id)}`);
|
|
2025
2219
|
console.log("");
|
|
2026
2220
|
console.log(
|
|
2027
|
-
|
|
2221
|
+
chalk15.yellow(
|
|
2028
2222
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2029
2223
|
)
|
|
2030
2224
|
);
|
|
2031
2225
|
}
|
|
2032
2226
|
process.exitCode = 0;
|
|
2033
2227
|
} catch (err) {
|
|
2034
|
-
|
|
2228
|
+
printError4(opts, err);
|
|
2035
2229
|
process.exitCode = 1;
|
|
2036
2230
|
}
|
|
2037
2231
|
}
|
|
2038
2232
|
async function ascKeyList(opts, deps) {
|
|
2039
|
-
const d = deps ??
|
|
2233
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2040
2234
|
try {
|
|
2041
2235
|
const { keys } = await d.listAscKeys();
|
|
2042
2236
|
if (opts.json) {
|
|
@@ -2046,7 +2240,7 @@ async function ascKeyList(opts, deps) {
|
|
|
2046
2240
|
}
|
|
2047
2241
|
process.exitCode = 0;
|
|
2048
2242
|
} catch (err) {
|
|
2049
|
-
|
|
2243
|
+
printError4(opts, err);
|
|
2050
2244
|
process.exitCode = 1;
|
|
2051
2245
|
}
|
|
2052
2246
|
}
|
|
@@ -2059,27 +2253,103 @@ function formatAscKeysHuman(keys) {
|
|
|
2059
2253
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2060
2254
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2061
2255
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2062
|
-
const lines = [
|
|
2256
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
2063
2257
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2064
2258
|
return lines.join("\n") + "\n";
|
|
2065
2259
|
}
|
|
2066
2260
|
async function ascKeyRm(name, opts, deps) {
|
|
2067
|
-
const d = deps ??
|
|
2261
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2068
2262
|
try {
|
|
2069
2263
|
await d.deleteAscKey(name);
|
|
2070
2264
|
if (opts.json) {
|
|
2071
2265
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2072
2266
|
} else {
|
|
2073
|
-
console.log(
|
|
2267
|
+
console.log(chalk15.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2074
2268
|
}
|
|
2075
2269
|
process.exitCode = 0;
|
|
2076
2270
|
} catch (err) {
|
|
2077
|
-
|
|
2271
|
+
printError4(opts, err);
|
|
2272
|
+
process.exitCode = 1;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
async function playKeyAdd(opts, deps) {
|
|
2276
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2277
|
+
if (!opts.file) {
|
|
2278
|
+
throw new UsageError(
|
|
2279
|
+
"--file is required (the service-account JSON from Google Play Console \u2192 API access)"
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
try {
|
|
2283
|
+
const file = await d.readFile(opts.file);
|
|
2284
|
+
const result = await d.addPlayKey({
|
|
2285
|
+
...opts.name !== void 0 ? { name: opts.name } : {},
|
|
2286
|
+
file,
|
|
2287
|
+
filename: basename2(opts.file)
|
|
2288
|
+
});
|
|
2289
|
+
if (opts.json) {
|
|
2290
|
+
console.log(JSON.stringify(result));
|
|
2291
|
+
} else {
|
|
2292
|
+
console.log(chalk15.green(`\u2713 added Google Play key ${result.name}`));
|
|
2293
|
+
console.log("");
|
|
2294
|
+
console.log(` Service account: ${chalk15.bold(result.client_email)}`);
|
|
2295
|
+
console.log("");
|
|
2296
|
+
console.log(
|
|
2297
|
+
chalk15.yellow(
|
|
2298
|
+
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2299
|
+
)
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
process.exitCode = 0;
|
|
2303
|
+
} catch (err) {
|
|
2304
|
+
printError4(opts, err);
|
|
2305
|
+
process.exitCode = 1;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
async function playKeyList(opts, deps) {
|
|
2309
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2310
|
+
try {
|
|
2311
|
+
const { keys } = await d.listPlayKeys();
|
|
2312
|
+
if (opts.json) {
|
|
2313
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
2314
|
+
} else {
|
|
2315
|
+
process.stdout.write(formatPlayKeysHuman(keys));
|
|
2316
|
+
}
|
|
2317
|
+
process.exitCode = 0;
|
|
2318
|
+
} catch (err) {
|
|
2319
|
+
printError4(opts, err);
|
|
2320
|
+
process.exitCode = 1;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
function formatPlayKeysHuman(keys) {
|
|
2324
|
+
if (keys.length === 0) {
|
|
2325
|
+
return "No Google Play keys found.\n";
|
|
2326
|
+
}
|
|
2327
|
+
const HEADERS3 = ["NAME", "CLIENT EMAIL", "CREATED"];
|
|
2328
|
+
const rows = keys.map((k) => [k.name, k.client_email, k.created_at ?? ""]);
|
|
2329
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2330
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2331
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2332
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
2333
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2334
|
+
return lines.join("\n") + "\n";
|
|
2335
|
+
}
|
|
2336
|
+
async function playKeyRm(name, opts, deps) {
|
|
2337
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2338
|
+
try {
|
|
2339
|
+
await d.deletePlayKey(name);
|
|
2340
|
+
if (opts.json) {
|
|
2341
|
+
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2342
|
+
} else {
|
|
2343
|
+
console.log(chalk15.green(`\u2713 removed Google Play key ${name}`));
|
|
2344
|
+
}
|
|
2345
|
+
process.exitCode = 0;
|
|
2346
|
+
} catch (err) {
|
|
2347
|
+
printError4(opts, err);
|
|
2078
2348
|
process.exitCode = 1;
|
|
2079
2349
|
}
|
|
2080
2350
|
}
|
|
2081
2351
|
async function credentialsList(opts, deps) {
|
|
2082
|
-
const d = deps ??
|
|
2352
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2083
2353
|
try {
|
|
2084
2354
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
2085
2355
|
for (const c of credentials2) {
|
|
@@ -2095,7 +2365,7 @@ async function credentialsList(opts, deps) {
|
|
|
2095
2365
|
}
|
|
2096
2366
|
process.exitCode = 0;
|
|
2097
2367
|
} catch (err) {
|
|
2098
|
-
|
|
2368
|
+
printError4(opts, err);
|
|
2099
2369
|
process.exitCode = 1;
|
|
2100
2370
|
}
|
|
2101
2371
|
}
|
|
@@ -2139,22 +2409,22 @@ function formatCredentialsHuman(credentials2) {
|
|
|
2139
2409
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2140
2410
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2141
2411
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2142
|
-
const lines = [
|
|
2412
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
2143
2413
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2144
2414
|
return lines.join("\n") + "\n";
|
|
2145
2415
|
}
|
|
2146
2416
|
async function credentialsRm(name, opts, deps) {
|
|
2147
|
-
const d = deps ??
|
|
2417
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2148
2418
|
try {
|
|
2149
2419
|
await d.deleteCredential({ name, app: opts.app });
|
|
2150
2420
|
if (opts.json) {
|
|
2151
2421
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2152
2422
|
} else {
|
|
2153
|
-
console.log(
|
|
2423
|
+
console.log(chalk15.green(`\u2713 removed ${name}`));
|
|
2154
2424
|
}
|
|
2155
2425
|
process.exitCode = 0;
|
|
2156
2426
|
} catch (err) {
|
|
2157
|
-
|
|
2427
|
+
printError4(opts, err);
|
|
2158
2428
|
process.exitCode = 1;
|
|
2159
2429
|
}
|
|
2160
2430
|
}
|
|
@@ -2162,16 +2432,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
2162
2432
|
const message = err instanceof Error ? err.message : String(err);
|
|
2163
2433
|
if (opts.json) {
|
|
2164
2434
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2165
|
-
console.error(
|
|
2435
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2166
2436
|
return;
|
|
2167
2437
|
}
|
|
2168
|
-
console.error(
|
|
2438
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2169
2439
|
console.error(
|
|
2170
|
-
|
|
2440
|
+
chalk15.red(
|
|
2171
2441
|
`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.`
|
|
2172
2442
|
)
|
|
2173
2443
|
);
|
|
2174
|
-
console.error(
|
|
2444
|
+
console.error(chalk15.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
2175
2445
|
console.error("");
|
|
2176
2446
|
console.error(` Key alias: ${result.key_alias}`);
|
|
2177
2447
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -2182,19 +2452,19 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
2182
2452
|
` Restore the file with: echo '${result.keystore_base64}' | base64 -d > ${result.name}.keystore`
|
|
2183
2453
|
);
|
|
2184
2454
|
}
|
|
2185
|
-
function
|
|
2455
|
+
function printError4(opts, err) {
|
|
2186
2456
|
const message = err instanceof Error ? err.message : String(err);
|
|
2187
2457
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2188
|
-
console.error(
|
|
2458
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
2189
2459
|
}
|
|
2190
2460
|
|
|
2191
2461
|
// src/commands/webhooks.ts
|
|
2192
|
-
import
|
|
2462
|
+
import chalk16 from "chalk";
|
|
2193
2463
|
function clientFor3(opts) {
|
|
2194
2464
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2195
2465
|
return new ApiClient(baseUrl, token);
|
|
2196
2466
|
}
|
|
2197
|
-
function
|
|
2467
|
+
function defaultDeps6(client) {
|
|
2198
2468
|
return {
|
|
2199
2469
|
addWebhook: (body) => client.addWebhook(body),
|
|
2200
2470
|
listWebhooks: () => client.listWebhooks(),
|
|
@@ -2211,7 +2481,7 @@ async function webhooksAdd(opts, deps) {
|
|
|
2211
2481
|
if (!opts.url.startsWith("https://")) {
|
|
2212
2482
|
throw new UsageError(`--url must start with https:// (got "${opts.url}")`);
|
|
2213
2483
|
}
|
|
2214
|
-
const d = deps ??
|
|
2484
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2215
2485
|
try {
|
|
2216
2486
|
const result = await d.addWebhook({
|
|
2217
2487
|
url: opts.url,
|
|
@@ -2220,19 +2490,19 @@ async function webhooksAdd(opts, deps) {
|
|
|
2220
2490
|
if (opts.json) {
|
|
2221
2491
|
console.log(JSON.stringify(result));
|
|
2222
2492
|
} else {
|
|
2223
|
-
console.log(
|
|
2493
|
+
console.log(chalk16.green(`\u2713 added ${result.id} ${result.url}`));
|
|
2224
2494
|
console.log("");
|
|
2225
|
-
console.log(` Signing secret: ${
|
|
2495
|
+
console.log(` Signing secret: ${chalk16.bold(result.secret)}`);
|
|
2226
2496
|
if (opts.secret !== void 0) {
|
|
2227
|
-
console.log(
|
|
2497
|
+
console.log(chalk16.yellow(" (This is the secret you provided.)"));
|
|
2228
2498
|
}
|
|
2229
2499
|
console.log(
|
|
2230
|
-
|
|
2500
|
+
chalk16.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
2231
2501
|
);
|
|
2232
2502
|
}
|
|
2233
2503
|
process.exitCode = 0;
|
|
2234
2504
|
} catch (err) {
|
|
2235
|
-
|
|
2505
|
+
printError5(opts, err);
|
|
2236
2506
|
process.exitCode = 1;
|
|
2237
2507
|
}
|
|
2238
2508
|
}
|
|
@@ -2258,12 +2528,12 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
2258
2528
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2259
2529
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2260
2530
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2261
|
-
const lines = [
|
|
2531
|
+
const lines = [chalk16.bold(join3(padCells(HEADERS3)))];
|
|
2262
2532
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2263
2533
|
return lines.join("\n") + "\n";
|
|
2264
2534
|
}
|
|
2265
2535
|
async function webhooksList(opts, deps) {
|
|
2266
|
-
const d = deps ??
|
|
2536
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2267
2537
|
try {
|
|
2268
2538
|
const { webhooks: webhooks2 } = await d.listWebhooks();
|
|
2269
2539
|
if (opts.json) {
|
|
@@ -2273,22 +2543,22 @@ async function webhooksList(opts, deps) {
|
|
|
2273
2543
|
}
|
|
2274
2544
|
process.exitCode = 0;
|
|
2275
2545
|
} catch (err) {
|
|
2276
|
-
|
|
2546
|
+
printError5(opts, err);
|
|
2277
2547
|
process.exitCode = 1;
|
|
2278
2548
|
}
|
|
2279
2549
|
}
|
|
2280
2550
|
async function webhooksRm(id, opts, deps) {
|
|
2281
|
-
const d = deps ??
|
|
2551
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2282
2552
|
try {
|
|
2283
2553
|
await d.deleteWebhook(id);
|
|
2284
2554
|
if (opts.json) {
|
|
2285
2555
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
2286
2556
|
} else {
|
|
2287
|
-
console.log(
|
|
2557
|
+
console.log(chalk16.green(`\u2713 removed ${id}`));
|
|
2288
2558
|
}
|
|
2289
2559
|
process.exitCode = 0;
|
|
2290
2560
|
} catch (err) {
|
|
2291
|
-
|
|
2561
|
+
printError5(opts, err);
|
|
2292
2562
|
process.exitCode = 1;
|
|
2293
2563
|
}
|
|
2294
2564
|
}
|
|
@@ -2305,7 +2575,7 @@ function parseUtcTimestamp(iso) {
|
|
|
2305
2575
|
var TEST_POLL_INTERVAL_MS = 3e3;
|
|
2306
2576
|
var TEST_POLL_BUDGET_MS = 6e4;
|
|
2307
2577
|
async function webhooksTest(id, opts, deps) {
|
|
2308
|
-
const d = deps ??
|
|
2578
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2309
2579
|
try {
|
|
2310
2580
|
const startedAt = d.now();
|
|
2311
2581
|
await d.testWebhook(id);
|
|
@@ -2317,13 +2587,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2317
2587
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
2318
2588
|
if (ld.status === "succeeded") {
|
|
2319
2589
|
if (!opts.json) {
|
|
2320
|
-
console.log(
|
|
2590
|
+
console.log(chalk16.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
2321
2591
|
}
|
|
2322
2592
|
process.exitCode = 0;
|
|
2323
2593
|
} else {
|
|
2324
2594
|
if (!opts.json) {
|
|
2325
2595
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
2326
|
-
console.error(
|
|
2596
|
+
console.error(chalk16.red(`\u2717 Failed (${code}) at ${ld.at}`));
|
|
2327
2597
|
}
|
|
2328
2598
|
process.exitCode = 1;
|
|
2329
2599
|
}
|
|
@@ -2332,24 +2602,24 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2332
2602
|
}
|
|
2333
2603
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
2334
2604
|
console.error(
|
|
2335
|
-
|
|
2605
|
+
chalk16.yellow(
|
|
2336
2606
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
2337
2607
|
)
|
|
2338
2608
|
);
|
|
2339
2609
|
process.exitCode = 1;
|
|
2340
2610
|
} catch (err) {
|
|
2341
|
-
|
|
2611
|
+
printError5(opts, err);
|
|
2342
2612
|
process.exitCode = 1;
|
|
2343
2613
|
}
|
|
2344
2614
|
}
|
|
2345
|
-
function
|
|
2615
|
+
function printError5(opts, err) {
|
|
2346
2616
|
const message = err instanceof Error ? err.message : String(err);
|
|
2347
2617
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2348
|
-
console.error(
|
|
2618
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2349
2619
|
}
|
|
2350
2620
|
|
|
2351
2621
|
// src/lib/formatters/devices.ts
|
|
2352
|
-
import
|
|
2622
|
+
import chalk17 from "chalk";
|
|
2353
2623
|
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
2354
2624
|
function formatDevicesHuman(devices) {
|
|
2355
2625
|
if (devices.length === 0) {
|
|
@@ -2365,9 +2635,9 @@ function formatDevicesHuman(devices) {
|
|
|
2365
2635
|
]);
|
|
2366
2636
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2367
2637
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2368
|
-
const lines = [
|
|
2638
|
+
const lines = [chalk17.bold(fmt(HEADERS2))];
|
|
2369
2639
|
for (const row of rows.sort(rowSort)) {
|
|
2370
|
-
const statusColored = row[5] === "available" ?
|
|
2640
|
+
const statusColored = row[5] === "available" ? chalk17.green(row[5]) : row[5] === "busy" ? chalk17.yellow(row[5]) : chalk17.dim(row[5]);
|
|
2371
2641
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
2372
2642
|
}
|
|
2373
2643
|
return lines.join("\n") + "\n";
|
|
@@ -2457,7 +2727,7 @@ import { dirname, join as join2 } from "path";
|
|
|
2457
2727
|
import { fileURLToPath } from "url";
|
|
2458
2728
|
import { spawnSync } from "child_process";
|
|
2459
2729
|
import { confirm } from "@inquirer/prompts";
|
|
2460
|
-
import
|
|
2730
|
+
import chalk18 from "chalk";
|
|
2461
2731
|
function resolveShippedSkillPath(fromDir) {
|
|
2462
2732
|
for (const rel of ["..", join2("..", "..")]) {
|
|
2463
2733
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -2485,23 +2755,23 @@ function registerMcp() {
|
|
|
2485
2755
|
encoding: "utf8"
|
|
2486
2756
|
});
|
|
2487
2757
|
if (res.status === 0) {
|
|
2488
|
-
console.log(
|
|
2758
|
+
console.log(chalk18.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
2489
2759
|
return;
|
|
2490
2760
|
}
|
|
2491
2761
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
2492
2762
|
if (out.includes("already exists")) {
|
|
2493
|
-
console.log(
|
|
2763
|
+
console.log(chalk18.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
2494
2764
|
return;
|
|
2495
2765
|
}
|
|
2496
|
-
console.error(
|
|
2497
|
-
console.error(
|
|
2766
|
+
console.error(chalk18.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
2767
|
+
console.error(chalk18.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
2498
2768
|
}
|
|
2499
2769
|
async function agentSetup(opts) {
|
|
2500
2770
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
2501
2771
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
2502
2772
|
if (!shipped) {
|
|
2503
2773
|
console.error(
|
|
2504
|
-
|
|
2774
|
+
chalk18.red(
|
|
2505
2775
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
2506
2776
|
)
|
|
2507
2777
|
);
|
|
@@ -2510,9 +2780,9 @@ async function agentSetup(opts) {
|
|
|
2510
2780
|
}
|
|
2511
2781
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
2512
2782
|
const result = installSkill(shipped, targetDir);
|
|
2513
|
-
console.log(
|
|
2783
|
+
console.log(chalk18.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
2514
2784
|
console.log(
|
|
2515
|
-
|
|
2785
|
+
chalk18.dim(
|
|
2516
2786
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
2517
2787
|
)
|
|
2518
2788
|
);
|
|
@@ -2520,7 +2790,7 @@ async function agentSetup(opts) {
|
|
|
2520
2790
|
if (!claudeCliAvailable()) {
|
|
2521
2791
|
if (opts.mcp === true) {
|
|
2522
2792
|
console.error(
|
|
2523
|
-
|
|
2793
|
+
chalk18.yellow(
|
|
2524
2794
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
2525
2795
|
)
|
|
2526
2796
|
);
|
|
@@ -2534,7 +2804,7 @@ async function agentSetup(opts) {
|
|
|
2534
2804
|
}
|
|
2535
2805
|
console.log(
|
|
2536
2806
|
`
|
|
2537
|
-
Suggested line for your project's ${
|
|
2807
|
+
Suggested line for your project's ${chalk18.bold("CLAUDE.md")} (or ${chalk18.bold(
|
|
2538
2808
|
"AGENTS.md"
|
|
2539
2809
|
)} for other agents):
|
|
2540
2810
|
`
|
|
@@ -2542,7 +2812,7 @@ Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bol
|
|
|
2542
2812
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
2543
2813
|
`);
|
|
2544
2814
|
console.log(
|
|
2545
|
-
|
|
2815
|
+
chalk18.dim(
|
|
2546
2816
|
"Other agents (Cursor, Copilot, Codex, ...): point them at the skill file above, or\nadd the MCP server per client \u2014 see https://vibeview.io/docs/agent-control"
|
|
2547
2817
|
)
|
|
2548
2818
|
);
|
|
@@ -2706,6 +2976,22 @@ program.command("build").description("Run your configured build command and uplo
|
|
|
2706
2976
|
"--cloud only: build a signed, installable production artifact (.aab/.ipa) using uploaded signing credentials (see `vibeview credentials add`), instead of the default unsigned simulator build",
|
|
2707
2977
|
false
|
|
2708
2978
|
).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
|
|
2979
|
+
program.command("submit").description(
|
|
2980
|
+
"Submit a succeeded production cloud build's artifact to TestFlight (ios/tvos) or a Google Play track (android/androidtv) \u2014 binary upload only; store listing and review stay in the store consoles"
|
|
2981
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--platform <platform>", "ios, tvos, android, or androidtv").option("--build <id>", "Cloud build id to submit (see `vibeview builds list`)").option(
|
|
2982
|
+
"--latest",
|
|
2983
|
+
"Submit the newest succeeded production build for --platform (pass exactly one of --build/--latest)",
|
|
2984
|
+
false
|
|
2985
|
+
).option(
|
|
2986
|
+
"--track <track>",
|
|
2987
|
+
"android/androidtv only: Play track \u2014 internal (default), alpha, beta, or production"
|
|
2988
|
+
).option(
|
|
2989
|
+
"--package <name>",
|
|
2990
|
+
"android/androidtv only: package name, for a build not linked to an app"
|
|
2991
|
+
).option(
|
|
2992
|
+
"--asc-key <name>",
|
|
2993
|
+
"ios/tvos only: which stored App Store Connect key to use (default when exactly one \u2014 see `vibeview credentials asc-key add`)"
|
|
2994
|
+
).option("--no-wait", "Print the submission id and exit without waiting for completion").option("--json", "Emit line-delimited JSON events on stdout instead of human output", false).action((opts) => submitCommand(opts));
|
|
2709
2995
|
var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
|
|
2710
2996
|
builds.command("list").description("List cloud builds in your organization").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw build summaries as a JSON array", false).option("--limit <n>", "Max builds to return (default 50, max 200)").action((opts) => buildsList(opts));
|
|
2711
2997
|
builds.command("logs <id>").description("Print a cloud build's log output").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print NDJSON log/outcome events instead of raw text", false).option("--follow", "Keep polling until the build reaches a terminal status", false).action((id, opts) => buildsLogs(id, opts));
|
|
@@ -2765,6 +3051,17 @@ ascKey.command("add").description(
|
|
|
2765
3051
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--key-id <id>", "The 10-character key id (shown next to the key in App Store Connect)").option("--issuer-id <id>", "The issuer id (UUID shown above the keys table)").option("--file <path>", "The AuthKey_<KEYID>.p8 file Apple let you download once").option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => ascKeyAdd(opts));
|
|
2766
3052
|
ascKey.command("list").description("List stored App Store Connect keys (metadata only \u2014 never the .p8)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => ascKeyList(opts));
|
|
2767
3053
|
ascKey.command("rm <name>").description("Remove a stored App Store Connect key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => ascKeyRm(name, opts));
|
|
3054
|
+
var playKey = credentials.command("play-key").description(
|
|
3055
|
+
"Manage Google Play service-account keys (create one in Google Play Console \u2192 API access, or Google Cloud \u2192 IAM \u2192 service accounts) \u2014 they power `vibeview submit --platform android|androidtv`"
|
|
3056
|
+
);
|
|
3057
|
+
playKey.command("add").description(
|
|
3058
|
+
"Store a Google Play service-account JSON key. The key is stored encrypted and never shown again; uploading the same name again rotates the key."
|
|
3059
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
|
|
3060
|
+
"--file <path>",
|
|
3061
|
+
"The service-account JSON key file (Google Play Console \u2192 API access / Google Cloud service accounts)"
|
|
3062
|
+
).option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => playKeyAdd(opts));
|
|
3063
|
+
playKey.command("list").description("List stored Google Play keys (metadata only \u2014 never the JSON key)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => playKeyList(opts));
|
|
3064
|
+
playKey.command("rm <name>").description("Remove a stored Google Play key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => playKeyRm(name, opts));
|
|
2768
3065
|
credentials.command("list").description("List signing credentials (never prints key material)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide credentials only)").option("--json", "Print the raw credential list as a JSON array", false).action((opts) => credentialsList(opts));
|
|
2769
3066
|
credentials.command("rm <name>").description("Remove a signing credential (replace by rm then add)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--json", "Print the result as one JSON line", false).action((name, opts) => credentialsRm(name, opts));
|
|
2770
3067
|
var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
|
|
@@ -2779,7 +3076,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
|
|
|
2779
3076
|
program.command("mcp").description(
|
|
2780
3077
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
2781
3078
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
2782
|
-
const { runMcpServer } = await import("./server-
|
|
3079
|
+
const { runMcpServer } = await import("./server-GMC6F545.js");
|
|
2783
3080
|
await runMcpServer(opts);
|
|
2784
3081
|
});
|
|
2785
3082
|
program.command("agent-setup").description(
|