vibeview 0.18.0 → 0.20.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 +64 -28
- package/dist/{chunk-MGP3UR7P.js → chunk-GUCKFB4K.js} +56 -7
- package/dist/{chunk-BJEDH672.js → chunk-MCCXPIGE.js} +80 -3
- package/dist/cli.js +607 -131
- package/dist/{server-TJQKNXDR.js → server-NR3ZPQF5.js} +3 -3
- package/dist/{upload-app-Y7MXT2PI.js → upload-app-HUMMA6UY.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,14 +6,16 @@ import {
|
|
|
6
6
|
clearDevState,
|
|
7
7
|
dev,
|
|
8
8
|
fetchFinalSummary,
|
|
9
|
+
formatDuration,
|
|
9
10
|
killOwnedMetro,
|
|
11
|
+
loadDevConfig,
|
|
10
12
|
newTransitionState,
|
|
11
13
|
printOutcomeLine,
|
|
12
14
|
printTransition,
|
|
13
15
|
readDevState,
|
|
14
16
|
readMetroState,
|
|
15
17
|
runAgentVerb
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-GUCKFB4K.js";
|
|
17
19
|
import {
|
|
18
20
|
ApiClient,
|
|
19
21
|
EXIT_INFRA_ERROR,
|
|
@@ -26,7 +28,7 @@ import {
|
|
|
26
28
|
resolveAuth,
|
|
27
29
|
saveConfig,
|
|
28
30
|
uploadApp
|
|
29
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-MCCXPIGE.js";
|
|
30
32
|
|
|
31
33
|
// src/cli.ts
|
|
32
34
|
import { createRequire } from "module";
|
|
@@ -1210,7 +1212,7 @@ async function buildsList(opts, deps) {
|
|
|
1210
1212
|
}
|
|
1211
1213
|
}
|
|
1212
1214
|
try {
|
|
1213
|
-
const { builds: builds2 } = await d.listCloudBuilds(limit);
|
|
1215
|
+
const { builds: builds2 } = await d.listCloudBuilds(limit !== void 0 ? { limit } : void 0);
|
|
1214
1216
|
if (opts.json) {
|
|
1215
1217
|
console.log(JSON.stringify(builds2, null, 2));
|
|
1216
1218
|
} else {
|
|
@@ -1378,9 +1380,357 @@ function printError(opts, err) {
|
|
|
1378
1380
|
console.error(chalk11.red(`error: ${message}`));
|
|
1379
1381
|
}
|
|
1380
1382
|
|
|
1381
|
-
// src/commands/
|
|
1383
|
+
// src/commands/submit.ts
|
|
1382
1384
|
import chalk12 from "chalk";
|
|
1383
|
-
function defaultDeps2(
|
|
1385
|
+
function defaultDeps2(client) {
|
|
1386
|
+
return {
|
|
1387
|
+
createSubmission: client.createSubmission.bind(client),
|
|
1388
|
+
getSubmission: client.getSubmission.bind(client),
|
|
1389
|
+
getSubmissionLog: client.getSubmissionLog.bind(client),
|
|
1390
|
+
listCloudBuilds: client.listCloudBuilds.bind(client)
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
var POLL_INTERVAL_MS2 = 2e3;
|
|
1394
|
+
var SUBMIT_PLATFORMS = ["ios", "tvos", "android", "androidtv"];
|
|
1395
|
+
function isSubmitPlatform(p) {
|
|
1396
|
+
return SUBMIT_PLATFORMS.includes(p);
|
|
1397
|
+
}
|
|
1398
|
+
var PLAY_TRACKS = ["internal", "alpha", "beta", "production"];
|
|
1399
|
+
var RELEASE_STATUSES = ["completed", "draft", "inProgress"];
|
|
1400
|
+
function submitConfigFor(cwd, platform) {
|
|
1401
|
+
let cfg;
|
|
1402
|
+
try {
|
|
1403
|
+
cfg = loadDevConfig(cwd);
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
console.error(
|
|
1406
|
+
chalk12.yellow(
|
|
1407
|
+
`warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
|
|
1408
|
+
)
|
|
1409
|
+
);
|
|
1410
|
+
return {};
|
|
1411
|
+
}
|
|
1412
|
+
const raw = cfg?.platforms?.[platform]?.submit;
|
|
1413
|
+
if (raw === void 0) return {};
|
|
1414
|
+
const path = `vibeview.json: platforms.${platform}.submit`;
|
|
1415
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1416
|
+
throw new UsageError(`${path} must be an object`);
|
|
1417
|
+
}
|
|
1418
|
+
const s = raw;
|
|
1419
|
+
for (const key of ["track", "releaseStatus", "ascKey"]) {
|
|
1420
|
+
if (s[key] !== void 0 && (typeof s[key] !== "string" || !s[key])) {
|
|
1421
|
+
throw new UsageError(`${path}.${key} must be a non-empty string`);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (s.rollout !== void 0 && typeof s.rollout !== "number") {
|
|
1425
|
+
throw new UsageError(`${path}.rollout must be a number strictly between 0 and 1`);
|
|
1426
|
+
}
|
|
1427
|
+
if (s.changesNotSentForReview !== void 0 && typeof s.changesNotSentForReview !== "boolean") {
|
|
1428
|
+
throw new UsageError(`${path}.changesNotSentForReview must be a boolean`);
|
|
1429
|
+
}
|
|
1430
|
+
return s;
|
|
1431
|
+
}
|
|
1432
|
+
function validateRolloutFraction(f, source, raw = f) {
|
|
1433
|
+
if (!Number.isFinite(f) || !(f > 0 && f < 1)) {
|
|
1434
|
+
throw new UsageError(
|
|
1435
|
+
`${source} must be a fraction strictly between 0 and 1 (e.g. 0.25) \u2014 got "${String(raw)}"`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
return f;
|
|
1439
|
+
}
|
|
1440
|
+
var SUBMISSION_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
|
|
1441
|
+
var MAX_CONSECUTIVE_POLL_FAILURES2 = 5;
|
|
1442
|
+
function emit(json, evt) {
|
|
1443
|
+
if (json) console.log(JSON.stringify(evt));
|
|
1444
|
+
}
|
|
1445
|
+
async function submitCommand(opts, deps) {
|
|
1446
|
+
if (!opts.platform || !isSubmitPlatform(opts.platform)) {
|
|
1447
|
+
throw new UsageError(
|
|
1448
|
+
`--platform is required and must be one of ${SUBMIT_PLATFORMS.join(", ")}`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const platform = opts.platform;
|
|
1452
|
+
const androidFamily = platform === "android" || platform === "androidtv";
|
|
1453
|
+
if (opts.build && opts.latest) {
|
|
1454
|
+
throw new UsageError("pass either --build <id> or --latest, not both");
|
|
1455
|
+
}
|
|
1456
|
+
if (!opts.build && !opts.latest) {
|
|
1457
|
+
throw new UsageError(
|
|
1458
|
+
"pass --build <id> (see `vibeview builds list`) or --latest (the newest succeeded production build for this platform)"
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
if (!androidFamily && opts.track !== void 0) {
|
|
1462
|
+
throw new UsageError(`--track only applies to android/androidtv (Play) \u2014 not ${platform}`);
|
|
1463
|
+
}
|
|
1464
|
+
if (!androidFamily && opts.package !== void 0) {
|
|
1465
|
+
throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${platform}`);
|
|
1466
|
+
}
|
|
1467
|
+
if (androidFamily && opts.ascKey !== void 0) {
|
|
1468
|
+
throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \u2014 not ${platform}`);
|
|
1469
|
+
}
|
|
1470
|
+
if (!androidFamily && opts.releaseStatus !== void 0) {
|
|
1471
|
+
throw new UsageError(
|
|
1472
|
+
`--release-status only applies to android/androidtv (Play) \u2014 not ${platform}`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
if (!androidFamily && opts.rollout !== void 0) {
|
|
1476
|
+
throw new UsageError(`--rollout only applies to android/androidtv (Play) \u2014 not ${platform}`);
|
|
1477
|
+
}
|
|
1478
|
+
const cfgDefaults = submitConfigFor(process.cwd(), platform);
|
|
1479
|
+
const track = opts.track ?? (androidFamily ? cfgDefaults.track : void 0);
|
|
1480
|
+
const releaseStatus = opts.releaseStatus ?? (androidFamily ? cfgDefaults.releaseStatus : void 0);
|
|
1481
|
+
const statusUnchanged = opts.releaseStatus === void 0 || opts.releaseStatus === cfgDefaults.releaseStatus;
|
|
1482
|
+
const rollout = opts.rollout !== void 0 ? validateRolloutFraction(Number(opts.rollout), "--rollout", opts.rollout) : androidFamily && statusUnchanged && cfgDefaults.rollout !== void 0 ? validateRolloutFraction(
|
|
1483
|
+
cfgDefaults.rollout,
|
|
1484
|
+
`vibeview.json platforms.${platform}.submit.rollout`
|
|
1485
|
+
) : void 0;
|
|
1486
|
+
const changesNotSentForReview = androidFamily ? cfgDefaults.changesNotSentForReview : void 0;
|
|
1487
|
+
const ascKey2 = opts.ascKey ?? (!androidFamily ? cfgDefaults.ascKey : void 0);
|
|
1488
|
+
if (track !== void 0 && !PLAY_TRACKS.includes(track)) {
|
|
1489
|
+
const source = opts.track !== void 0 ? "--track" : `vibeview.json platforms.${platform}.submit.track`;
|
|
1490
|
+
throw new UsageError(`${source} must be one of ${PLAY_TRACKS.join(", ")} \u2014 got "${track}"`);
|
|
1491
|
+
}
|
|
1492
|
+
if (releaseStatus !== void 0 && !RELEASE_STATUSES.includes(releaseStatus)) {
|
|
1493
|
+
const source = opts.releaseStatus !== void 0 ? "--release-status" : `vibeview.json platforms.${platform}.submit.releaseStatus`;
|
|
1494
|
+
throw new UsageError(
|
|
1495
|
+
`${source} must be one of ${RELEASE_STATUSES.join(", ")} \u2014 got "${releaseStatus}"`
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
if (rollout !== void 0 && releaseStatus !== "inProgress") {
|
|
1499
|
+
throw new UsageError(
|
|
1500
|
+
`a rollout fraction requires release status "inProgress" (a staged rollout) \u2014 got "${releaseStatus ?? "completed"}"`
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
if (releaseStatus === "inProgress" && rollout === void 0) {
|
|
1504
|
+
throw new UsageError(
|
|
1505
|
+
`release status "inProgress" requires a rollout fraction \u2014 pass --rollout (e.g. --rollout 0.1)`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1509
|
+
const client = new ApiClient(baseUrl, token);
|
|
1510
|
+
const d = deps ?? defaultDeps2(client);
|
|
1511
|
+
try {
|
|
1512
|
+
const buildId = opts.build ?? await resolveLatestBuild(platform, d);
|
|
1513
|
+
const sub = await d.createSubmission({
|
|
1514
|
+
build: buildId,
|
|
1515
|
+
platform,
|
|
1516
|
+
...track !== void 0 ? { track } : {},
|
|
1517
|
+
...opts.package !== void 0 ? { package: opts.package } : {},
|
|
1518
|
+
...ascKey2 !== void 0 ? { ascKey: ascKey2 } : {},
|
|
1519
|
+
...releaseStatus !== void 0 ? { releaseStatus } : {},
|
|
1520
|
+
...rollout !== void 0 ? { rollout } : {},
|
|
1521
|
+
...changesNotSentForReview !== void 0 ? { changesNotSentForReview } : {}
|
|
1522
|
+
});
|
|
1523
|
+
emit(opts.json, {
|
|
1524
|
+
event: "submission_created",
|
|
1525
|
+
id: sub.id,
|
|
1526
|
+
build_id: sub.build_id,
|
|
1527
|
+
platform: sub.platform
|
|
1528
|
+
});
|
|
1529
|
+
if (opts.wait === false) {
|
|
1530
|
+
if (!opts.json) console.log(sub.id);
|
|
1531
|
+
process.exitCode = 0;
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (!opts.json) {
|
|
1535
|
+
console.error(` ${chalk12.cyan(sub.id)} ${chalk12.dim(`[${sub.platform}]`)}`);
|
|
1536
|
+
}
|
|
1537
|
+
const final = await followSubmission(sub, opts, d);
|
|
1538
|
+
if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
|
|
1539
|
+
console.error(
|
|
1540
|
+
chalk12.dim("release status: draft \u2014 activate it from the Play Console when ready")
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
printError2(opts, err);
|
|
1546
|
+
process.exitCode = 1;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
async function resolveLatestBuild(platform, deps) {
|
|
1550
|
+
const { builds: builds2 } = await deps.listCloudBuilds({
|
|
1551
|
+
platform,
|
|
1552
|
+
status: "succeeded",
|
|
1553
|
+
distribution: "production",
|
|
1554
|
+
limit: 200
|
|
1555
|
+
});
|
|
1556
|
+
const match = builds2.find(
|
|
1557
|
+
(b) => b.platform === platform && b.distribution === "production" && b.status === "succeeded" && b.has_artifact
|
|
1558
|
+
);
|
|
1559
|
+
if (!match) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
`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\`).`
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
if (match.platform !== platform) {
|
|
1565
|
+
throw new Error(
|
|
1566
|
+
`--latest resolved build ${match.id}, which is ${match.platform}, not ${platform} \u2014 refusing to submit it.`
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
return match.id;
|
|
1570
|
+
}
|
|
1571
|
+
async function followSubmission(created, opts, deps) {
|
|
1572
|
+
const pollIntervalMs = deps.pollIntervalMs ?? POLL_INTERVAL_MS2;
|
|
1573
|
+
let offset = 0;
|
|
1574
|
+
let status = created.status;
|
|
1575
|
+
let consecutiveFailures = 0;
|
|
1576
|
+
let printedQueued = false;
|
|
1577
|
+
let printedRunning = false;
|
|
1578
|
+
while (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
|
|
1579
|
+
try {
|
|
1580
|
+
const log = await deps.getSubmissionLog(created.id, offset);
|
|
1581
|
+
if (log.status === "queued" && !printedQueued) {
|
|
1582
|
+
printedQueued = true;
|
|
1583
|
+
if (opts.json) {
|
|
1584
|
+
emit(opts.json, { event: "status", id: created.id, status: "queued" });
|
|
1585
|
+
} else {
|
|
1586
|
+
console.error(chalk12.dim("\u23F3 queued \u2014 waiting for a submission slot"));
|
|
1587
|
+
}
|
|
1588
|
+
} else if (log.status === "running" && !printedRunning) {
|
|
1589
|
+
printedRunning = true;
|
|
1590
|
+
if (opts.json) {
|
|
1591
|
+
emit(opts.json, { event: "status", id: created.id, status: "running" });
|
|
1592
|
+
} else {
|
|
1593
|
+
console.error(`${chalk12.cyan("\u25B6")} submitting`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (log.content) {
|
|
1597
|
+
if (opts.json) {
|
|
1598
|
+
emit(opts.json, { event: "submission_log", id: created.id, content: log.content });
|
|
1599
|
+
} else {
|
|
1600
|
+
process.stdout.write(log.content);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
offset = log.offset;
|
|
1604
|
+
status = log.status;
|
|
1605
|
+
consecutiveFailures = 0;
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
if (err instanceof NotFoundError) throw err;
|
|
1608
|
+
consecutiveFailures++;
|
|
1609
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES2) {
|
|
1610
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`submission poll failed ${consecutiveFailures} times in a row, giving up: ${message}
|
|
1613
|
+
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).`
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
|
|
1618
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
let final;
|
|
1622
|
+
try {
|
|
1623
|
+
final = await deps.getSubmission(created.id);
|
|
1624
|
+
} catch {
|
|
1625
|
+
final = { ...created, status };
|
|
1626
|
+
}
|
|
1627
|
+
emit(opts.json, {
|
|
1628
|
+
event: "submission_finished",
|
|
1629
|
+
id: final.id,
|
|
1630
|
+
status: final.status,
|
|
1631
|
+
error_summary: final.error_summary
|
|
1632
|
+
});
|
|
1633
|
+
if (!opts.json) {
|
|
1634
|
+
const ok = final.status === "succeeded";
|
|
1635
|
+
const mark = ok ? chalk12.green("\u2713") : chalk12.red("\u2717");
|
|
1636
|
+
const duration = formatDuration(final.started_at, final.finished_at);
|
|
1637
|
+
let line = `${mark} ${final.status}`;
|
|
1638
|
+
if (duration) line += ` (${duration})`;
|
|
1639
|
+
if (final.error_summary) line += ` \u2014 ${final.error_summary}`;
|
|
1640
|
+
console.error(line);
|
|
1641
|
+
}
|
|
1642
|
+
return final;
|
|
1643
|
+
}
|
|
1644
|
+
function printError2(opts, err) {
|
|
1645
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1646
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1647
|
+
console.error(chalk12.red(`error: ${message}`));
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/commands/versions.ts
|
|
1651
|
+
import chalk13 from "chalk";
|
|
1652
|
+
function defaultDeps3(client) {
|
|
1653
|
+
return {
|
|
1654
|
+
listAppVersions: client.listAppVersions.bind(client),
|
|
1655
|
+
setAppVersion: client.setAppVersion.bind(client)
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
var VERSION_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
1659
|
+
async function versionsList(opts, deps) {
|
|
1660
|
+
if (!opts.app) {
|
|
1661
|
+
throw new UsageError("--app <id> is required (see `vibeview list-apps` for ids)");
|
|
1662
|
+
}
|
|
1663
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1664
|
+
const client = new ApiClient(baseUrl, token);
|
|
1665
|
+
const d = deps ?? defaultDeps3(client);
|
|
1666
|
+
try {
|
|
1667
|
+
const response = await d.listAppVersions(opts.app);
|
|
1668
|
+
if (opts.json) {
|
|
1669
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1670
|
+
} else {
|
|
1671
|
+
process.stdout.write(formatVersionsHuman(response.versions));
|
|
1672
|
+
}
|
|
1673
|
+
process.exitCode = 0;
|
|
1674
|
+
} catch (err) {
|
|
1675
|
+
printError3(opts, err);
|
|
1676
|
+
process.exitCode = 1;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
async function versionsSet(buildNumber, opts, deps) {
|
|
1680
|
+
if (!opts.app) {
|
|
1681
|
+
throw new UsageError("--app <id> is required (see `vibeview list-apps` for ids)");
|
|
1682
|
+
}
|
|
1683
|
+
if (!opts.platform || !VERSION_PLATFORMS.includes(opts.platform)) {
|
|
1684
|
+
throw new UsageError(
|
|
1685
|
+
`--platform is required and must be one of ${VERSION_PLATFORMS.join(", ")}`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
const parsed = /^\d+$/.test(buildNumber) ? Number(buildNumber) : NaN;
|
|
1689
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
1690
|
+
throw new UsageError(`build number must be a non-negative integer \u2014 got "${buildNumber}"`);
|
|
1691
|
+
}
|
|
1692
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1693
|
+
const client = new ApiClient(baseUrl, token);
|
|
1694
|
+
const d = deps ?? defaultDeps3(client);
|
|
1695
|
+
try {
|
|
1696
|
+
const version = await d.setAppVersion(opts.app, opts.platform, parsed);
|
|
1697
|
+
if (opts.json) {
|
|
1698
|
+
console.log(JSON.stringify(version));
|
|
1699
|
+
} else {
|
|
1700
|
+
console.log(
|
|
1701
|
+
chalk13.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk13.dim(
|
|
1702
|
+
" \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
process.exitCode = 0;
|
|
1707
|
+
} catch (err) {
|
|
1708
|
+
printError3(opts, err);
|
|
1709
|
+
process.exitCode = 1;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
var HEADERS2 = ["PLATFORM", "BUILD NUMBER", "UPDATED"];
|
|
1713
|
+
function formatVersionsHuman(versions2) {
|
|
1714
|
+
if (versions2.length === 0) {
|
|
1715
|
+
return "No version counters yet \u2014 they appear after the first autoIncrement production build, or seed one with `vibeview versions set`.\n";
|
|
1716
|
+
}
|
|
1717
|
+
const rows = versions2.map((v) => [v.platform, String(v.build_number), v.updated_at ?? ""]);
|
|
1718
|
+
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1719
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1720
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1721
|
+
const lines = [chalk13.bold(join3(padCells(HEADERS2)))];
|
|
1722
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1723
|
+
return lines.join("\n") + "\n";
|
|
1724
|
+
}
|
|
1725
|
+
function printError3(opts, err) {
|
|
1726
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1727
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1728
|
+
console.error(chalk13.red(`error: ${message}`));
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/commands/whoami.ts
|
|
1732
|
+
import chalk14 from "chalk";
|
|
1733
|
+
function defaultDeps4(opts) {
|
|
1384
1734
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1385
1735
|
const client = new ApiClient(baseUrl, token);
|
|
1386
1736
|
return { fetchMe: () => client.whoAmI() };
|
|
@@ -1392,7 +1742,7 @@ function effectiveRole(role, tokenRole) {
|
|
|
1392
1742
|
return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
|
|
1393
1743
|
}
|
|
1394
1744
|
async function whoAmI(opts, deps) {
|
|
1395
|
-
const d = deps ??
|
|
1745
|
+
const d = deps ?? defaultDeps4(opts);
|
|
1396
1746
|
try {
|
|
1397
1747
|
const me = await d.fetchMe();
|
|
1398
1748
|
const effective = effectiveRole(me.role, me.token_role);
|
|
@@ -1401,27 +1751,27 @@ async function whoAmI(opts, deps) {
|
|
|
1401
1751
|
process.exitCode = 0;
|
|
1402
1752
|
return;
|
|
1403
1753
|
}
|
|
1404
|
-
console.log(`${
|
|
1754
|
+
console.log(`${chalk14.bold("Logged in as")} ${me.email}`);
|
|
1405
1755
|
if (me.memberships.length === 1) {
|
|
1406
|
-
console.log(`${
|
|
1756
|
+
console.log(`${chalk14.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
1407
1757
|
} else if (me.memberships.length > 1) {
|
|
1408
|
-
console.log(
|
|
1758
|
+
console.log(chalk14.bold("Organizations"));
|
|
1409
1759
|
for (const m of me.memberships) {
|
|
1410
1760
|
console.log(` ${m.organization_name} (${m.role})`);
|
|
1411
1761
|
}
|
|
1412
1762
|
}
|
|
1413
|
-
console.log(`${
|
|
1414
|
-
console.log(`${
|
|
1415
|
-
console.log(`${
|
|
1763
|
+
console.log(`${chalk14.bold("Member role")} ${me.role ?? "\u2014"}`);
|
|
1764
|
+
console.log(`${chalk14.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
|
|
1765
|
+
console.log(`${chalk14.bold("Effective role")} ${effective ?? "\u2014"}`);
|
|
1416
1766
|
if (me.is_superadmin) {
|
|
1417
|
-
console.log(
|
|
1767
|
+
console.log(chalk14.dim("Superadmin (platform-wide; does not raise the org role above)"));
|
|
1418
1768
|
}
|
|
1419
1769
|
if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
|
|
1420
1770
|
console.log(
|
|
1421
|
-
|
|
1771
|
+
chalk14.yellow(
|
|
1422
1772
|
`
|
|
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 ${
|
|
1773
|
+
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 ${chalk14.bold("vibeview env set")}) will fail with a permissions error.
|
|
1774
|
+
Create an admin API token in the dashboard and use ${chalk14.bold("--token")} or ${chalk14.bold("VIBEVIEW_API_TOKEN")}.`
|
|
1425
1775
|
)
|
|
1426
1776
|
);
|
|
1427
1777
|
}
|
|
@@ -1431,7 +1781,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
|
|
|
1431
1781
|
if (opts.json) {
|
|
1432
1782
|
console.log(JSON.stringify({ event: "error", message }));
|
|
1433
1783
|
}
|
|
1434
|
-
console.error(
|
|
1784
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1435
1785
|
process.exitCode = 1;
|
|
1436
1786
|
}
|
|
1437
1787
|
}
|
|
@@ -1439,7 +1789,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
|
|
|
1439
1789
|
// src/commands/env.ts
|
|
1440
1790
|
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
1441
1791
|
import { basename } from "path";
|
|
1442
|
-
import
|
|
1792
|
+
import chalk15 from "chalk";
|
|
1443
1793
|
import { password } from "@inquirer/prompts";
|
|
1444
1794
|
|
|
1445
1795
|
// src/lib/gitignore.ts
|
|
@@ -1466,7 +1816,7 @@ async function writeSecureFile(path, content) {
|
|
|
1466
1816
|
}
|
|
1467
1817
|
await chmod(path, 384);
|
|
1468
1818
|
}
|
|
1469
|
-
function
|
|
1819
|
+
function defaultDeps5(client) {
|
|
1470
1820
|
return {
|
|
1471
1821
|
listVars: (app) => client.listBuildEnvVars(app),
|
|
1472
1822
|
setVar: (body) => client.setBuildEnvVar(body),
|
|
@@ -1495,11 +1845,11 @@ function defaultDeps3(client) {
|
|
|
1495
1845
|
isIgnored: (path) => isGitIgnored(path),
|
|
1496
1846
|
writeFile: writeSecureFile,
|
|
1497
1847
|
readFile: (path) => readFileAsync(path),
|
|
1498
|
-
warn: (message) => console.error(
|
|
1848
|
+
warn: (message) => console.error(chalk15.yellow(`warning: ${message}`))
|
|
1499
1849
|
};
|
|
1500
1850
|
}
|
|
1501
1851
|
async function envList(opts, deps) {
|
|
1502
|
-
const d = deps ??
|
|
1852
|
+
const d = deps ?? defaultDeps5(clientFor(opts));
|
|
1503
1853
|
try {
|
|
1504
1854
|
const { variables } = await d.listVars(opts.app);
|
|
1505
1855
|
if (opts.json) {
|
|
@@ -1509,7 +1859,7 @@ async function envList(opts, deps) {
|
|
|
1509
1859
|
}
|
|
1510
1860
|
process.exitCode = 0;
|
|
1511
1861
|
} catch (err) {
|
|
1512
|
-
|
|
1862
|
+
printError4(opts, err);
|
|
1513
1863
|
process.exitCode = 1;
|
|
1514
1864
|
}
|
|
1515
1865
|
}
|
|
@@ -1517,22 +1867,22 @@ function formatEnvVarsHuman(variables) {
|
|
|
1517
1867
|
if (variables.length === 0) {
|
|
1518
1868
|
return "No build variables found.\n";
|
|
1519
1869
|
}
|
|
1520
|
-
const
|
|
1870
|
+
const HEADERS4 = ["NAME", "VISIBILITY", "SCOPE", "HINT"];
|
|
1521
1871
|
const rows = variables.map((v) => [
|
|
1522
1872
|
v.name,
|
|
1523
1873
|
v.is_file ? `${v.visibility} (file)` : v.visibility,
|
|
1524
1874
|
v.app_public_id ?? "org-wide",
|
|
1525
1875
|
v.value_hint ?? ""
|
|
1526
1876
|
]);
|
|
1527
|
-
const widths =
|
|
1877
|
+
const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1528
1878
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1529
1879
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1530
|
-
const lines = [
|
|
1880
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS4)))];
|
|
1531
1881
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1532
1882
|
return lines.join("\n") + "\n";
|
|
1533
1883
|
}
|
|
1534
1884
|
async function envSet(name, value, opts, deps) {
|
|
1535
|
-
const d = deps ??
|
|
1885
|
+
const d = deps ?? defaultDeps5(clientFor(opts));
|
|
1536
1886
|
let resolvedValue = value;
|
|
1537
1887
|
if (resolvedValue === void 0 && !opts.file) {
|
|
1538
1888
|
if (opts.json || !d.isTTY) {
|
|
@@ -1558,7 +1908,7 @@ async function envSet(name, value, opts, deps) {
|
|
|
1558
1908
|
printSetResult(opts, result);
|
|
1559
1909
|
process.exitCode = 0;
|
|
1560
1910
|
} catch (err) {
|
|
1561
|
-
|
|
1911
|
+
printError4(opts, err);
|
|
1562
1912
|
process.exitCode = 1;
|
|
1563
1913
|
}
|
|
1564
1914
|
}
|
|
@@ -1577,26 +1927,26 @@ function printSetResult(opts, result) {
|
|
|
1577
1927
|
if (opts.json) {
|
|
1578
1928
|
console.log(JSON.stringify(result));
|
|
1579
1929
|
} else {
|
|
1580
|
-
console.log(
|
|
1930
|
+
console.log(chalk15.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
1581
1931
|
}
|
|
1582
1932
|
}
|
|
1583
1933
|
async function envRm(name, opts, deps) {
|
|
1584
|
-
const d = deps ??
|
|
1934
|
+
const d = deps ?? defaultDeps5(clientFor(opts));
|
|
1585
1935
|
try {
|
|
1586
1936
|
await d.rmVar({ name, app: opts.app });
|
|
1587
1937
|
if (opts.json) {
|
|
1588
1938
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
1589
1939
|
} else {
|
|
1590
|
-
console.log(
|
|
1940
|
+
console.log(chalk15.green(`\u2713 removed ${name}`));
|
|
1591
1941
|
}
|
|
1592
1942
|
process.exitCode = 0;
|
|
1593
1943
|
} catch (err) {
|
|
1594
|
-
|
|
1944
|
+
printError4(opts, err);
|
|
1595
1945
|
process.exitCode = 1;
|
|
1596
1946
|
}
|
|
1597
1947
|
}
|
|
1598
1948
|
async function envPull(opts, deps) {
|
|
1599
|
-
const d = deps ??
|
|
1949
|
+
const d = deps ?? defaultDeps5(clientFor(opts));
|
|
1600
1950
|
const outPath = opts.out ?? ".env";
|
|
1601
1951
|
try {
|
|
1602
1952
|
const { variables } = await d.pullVars(opts.app);
|
|
@@ -1619,11 +1969,11 @@ async function envPull(opts, deps) {
|
|
|
1619
1969
|
if (opts.json) {
|
|
1620
1970
|
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
1621
1971
|
} else {
|
|
1622
|
-
console.log(
|
|
1972
|
+
console.log(chalk15.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
1623
1973
|
}
|
|
1624
1974
|
process.exitCode = 0;
|
|
1625
1975
|
} catch (err) {
|
|
1626
|
-
|
|
1976
|
+
printError4(opts, err);
|
|
1627
1977
|
process.exitCode = 1;
|
|
1628
1978
|
}
|
|
1629
1979
|
}
|
|
@@ -1655,10 +2005,10 @@ function escapeEnvValue(value) {
|
|
|
1655
2005
|
}
|
|
1656
2006
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1657
2007
|
}
|
|
1658
|
-
function
|
|
2008
|
+
function printError4(opts, err) {
|
|
1659
2009
|
const message = err instanceof Error ? err.message : String(err);
|
|
1660
2010
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1661
|
-
console.error(
|
|
2011
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
1662
2012
|
}
|
|
1663
2013
|
|
|
1664
2014
|
// src/commands/credentials.ts
|
|
@@ -1670,13 +2020,13 @@ import {
|
|
|
1670
2020
|
} from "fs/promises";
|
|
1671
2021
|
import { mkdirSync } from "fs";
|
|
1672
2022
|
import { basename as basename2, extname, join } from "path";
|
|
1673
|
-
import
|
|
2023
|
+
import chalk16 from "chalk";
|
|
1674
2024
|
import { input, password as password2 } from "@inquirer/prompts";
|
|
1675
2025
|
function clientFor2(opts) {
|
|
1676
2026
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1677
2027
|
return new ApiClient(baseUrl, token);
|
|
1678
2028
|
}
|
|
1679
|
-
function
|
|
2029
|
+
function defaultDeps6(client) {
|
|
1680
2030
|
return {
|
|
1681
2031
|
addCredential: (body) => client.addSigningCredential({
|
|
1682
2032
|
platform: body.platform,
|
|
@@ -1721,6 +2071,16 @@ function defaultDeps4(client) {
|
|
|
1721
2071
|
}
|
|
1722
2072
|
await client.deleteAscKey(match.id);
|
|
1723
2073
|
},
|
|
2074
|
+
addPlayKey: (body) => client.addPlayKey(body),
|
|
2075
|
+
listPlayKeys: () => client.listPlayKeys(),
|
|
2076
|
+
deletePlayKey: async (name) => {
|
|
2077
|
+
const { keys } = await client.listPlayKeys();
|
|
2078
|
+
const match = keys.find((k) => k.name === name);
|
|
2079
|
+
if (!match) {
|
|
2080
|
+
throw new Error(`No Google Play key named "${name}" found.`);
|
|
2081
|
+
}
|
|
2082
|
+
await client.deletePlayKey(match.id);
|
|
2083
|
+
},
|
|
1724
2084
|
prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
|
|
1725
2085
|
isTTY: Boolean(process.stdin.isTTY),
|
|
1726
2086
|
readFile: (path) => readFileAsync2(path),
|
|
@@ -1733,7 +2093,7 @@ function defaultDeps4(client) {
|
|
|
1733
2093
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
1734
2094
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
1735
2095
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
1736
|
-
warn: (message) => console.error(
|
|
2096
|
+
warn: (message) => console.error(chalk16.yellow(`warning: ${message}`))
|
|
1737
2097
|
};
|
|
1738
2098
|
}
|
|
1739
2099
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -1743,7 +2103,7 @@ function isCredentialPlatform(p) {
|
|
|
1743
2103
|
return CREDENTIAL_PLATFORMS.includes(p);
|
|
1744
2104
|
}
|
|
1745
2105
|
async function credentialsAdd(opts, deps) {
|
|
1746
|
-
const d = deps ??
|
|
2106
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
1747
2107
|
if (!opts.platform || !isCredentialPlatform(opts.platform)) {
|
|
1748
2108
|
throw new UsageError('--platform is required and must be "ios", "android", or "tvos"');
|
|
1749
2109
|
}
|
|
@@ -1802,16 +2162,16 @@ async function credentialsAdd(opts, deps) {
|
|
|
1802
2162
|
if (opts.json) {
|
|
1803
2163
|
console.log(JSON.stringify(result));
|
|
1804
2164
|
} else {
|
|
1805
|
-
console.log(
|
|
2165
|
+
console.log(chalk16.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
1806
2166
|
}
|
|
1807
2167
|
process.exitCode = 0;
|
|
1808
2168
|
} catch (err) {
|
|
1809
|
-
|
|
2169
|
+
printError5(opts, err);
|
|
1810
2170
|
process.exitCode = 1;
|
|
1811
2171
|
}
|
|
1812
2172
|
}
|
|
1813
2173
|
async function credentialsGenerate(opts, deps) {
|
|
1814
|
-
const d = deps ??
|
|
2174
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
1815
2175
|
if (!opts.platform) {
|
|
1816
2176
|
throw new UsageError('--platform is required and must be "android", "ios", or "tvos"');
|
|
1817
2177
|
}
|
|
@@ -1860,27 +2220,27 @@ async function credentialsGenerate(opts, deps) {
|
|
|
1860
2220
|
}
|
|
1861
2221
|
if (opts.json) {
|
|
1862
2222
|
} else {
|
|
1863
|
-
console.log(
|
|
2223
|
+
console.log(chalk16.green(`\u2713 generated ${result.name} (android)`));
|
|
1864
2224
|
console.log("");
|
|
1865
|
-
console.log(` Keystore saved to: ${
|
|
1866
|
-
console.log(` Key alias: ${
|
|
1867
|
-
console.log(` Store password: ${
|
|
1868
|
-
console.log(` Key password: ${
|
|
2225
|
+
console.log(` Keystore saved to: ${chalk16.bold(savedTo)}`);
|
|
2226
|
+
console.log(` Key alias: ${chalk16.bold(result.key_alias)}`);
|
|
2227
|
+
console.log(` Store password: ${chalk16.bold(result.store_password)}`);
|
|
2228
|
+
console.log(` Key password: ${chalk16.bold(result.key_password)}`);
|
|
1869
2229
|
console.log("");
|
|
1870
2230
|
console.log(
|
|
1871
|
-
|
|
2231
|
+
chalk16.yellow(
|
|
1872
2232
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
1873
2233
|
)
|
|
1874
2234
|
);
|
|
1875
2235
|
console.log(
|
|
1876
|
-
|
|
2236
|
+
chalk16.yellow(
|
|
1877
2237
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
1878
2238
|
)
|
|
1879
2239
|
);
|
|
1880
2240
|
}
|
|
1881
2241
|
process.exitCode = 0;
|
|
1882
2242
|
} catch (err) {
|
|
1883
|
-
|
|
2243
|
+
printError5(opts, err);
|
|
1884
2244
|
process.exitCode = 1;
|
|
1885
2245
|
}
|
|
1886
2246
|
}
|
|
@@ -1899,29 +2259,29 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
1899
2259
|
if (opts.json) {
|
|
1900
2260
|
console.log(JSON.stringify(result));
|
|
1901
2261
|
} else {
|
|
1902
|
-
console.log(
|
|
2262
|
+
console.log(chalk16.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
1903
2263
|
console.log("");
|
|
1904
|
-
console.log(` Scope: ${
|
|
2264
|
+
console.log(` Scope: ${chalk16.bold(result.app_public_id ?? "org-wide")}`);
|
|
1905
2265
|
const expires = formatExpires(result);
|
|
1906
2266
|
if (expires) {
|
|
1907
|
-
console.log(` Expires: ${
|
|
2267
|
+
console.log(` Expires: ${chalk16.bold(expires)}`);
|
|
1908
2268
|
}
|
|
1909
2269
|
console.log("");
|
|
1910
2270
|
console.log(
|
|
1911
2271
|
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
1912
2272
|
);
|
|
1913
2273
|
console.log(
|
|
1914
|
-
` Run ${
|
|
2274
|
+
` Run ${chalk16.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
1915
2275
|
);
|
|
1916
2276
|
}
|
|
1917
2277
|
process.exitCode = 0;
|
|
1918
2278
|
} catch (err) {
|
|
1919
|
-
|
|
2279
|
+
printError5(opts, err);
|
|
1920
2280
|
process.exitCode = 1;
|
|
1921
2281
|
}
|
|
1922
2282
|
}
|
|
1923
2283
|
async function credentialsDownload(name, opts, deps) {
|
|
1924
|
-
const d = deps ??
|
|
2284
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
1925
2285
|
const outDir = opts.out ?? process.cwd();
|
|
1926
2286
|
try {
|
|
1927
2287
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
@@ -1977,26 +2337,26 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
1977
2337
|
await d.chmod(path, 384);
|
|
1978
2338
|
}
|
|
1979
2339
|
if (!opts.json) {
|
|
1980
|
-
console.log(
|
|
2340
|
+
console.log(chalk16.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
1981
2341
|
console.log("");
|
|
1982
|
-
console.log(` Saved to: ${
|
|
2342
|
+
console.log(` Saved to: ${chalk16.bold(savedTo)}`);
|
|
1983
2343
|
if (profileSavedTo !== null) {
|
|
1984
|
-
console.log(` Profile: ${
|
|
2344
|
+
console.log(` Profile: ${chalk16.bold(profileSavedTo)}`);
|
|
1985
2345
|
}
|
|
1986
2346
|
for (const [key, value] of Object.entries(material.passwords)) {
|
|
1987
|
-
console.log(` ${key}: ${
|
|
2347
|
+
console.log(` ${key}: ${chalk16.bold(value)}`);
|
|
1988
2348
|
}
|
|
1989
2349
|
console.log("");
|
|
1990
|
-
console.log(
|
|
2350
|
+
console.log(chalk16.yellow(" This download is recorded in your organization's audit log."));
|
|
1991
2351
|
}
|
|
1992
2352
|
process.exitCode = 0;
|
|
1993
2353
|
} catch (err) {
|
|
1994
|
-
|
|
2354
|
+
printError5(opts, err);
|
|
1995
2355
|
process.exitCode = 1;
|
|
1996
2356
|
}
|
|
1997
2357
|
}
|
|
1998
2358
|
async function ascKeyAdd(opts, deps) {
|
|
1999
|
-
const d = deps ??
|
|
2359
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2000
2360
|
if (!opts.keyId) {
|
|
2001
2361
|
throw new UsageError("--key-id is required (the 10-character App Store Connect key id)");
|
|
2002
2362
|
}
|
|
@@ -2018,25 +2378,25 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2018
2378
|
if (opts.json) {
|
|
2019
2379
|
console.log(JSON.stringify(result));
|
|
2020
2380
|
} else {
|
|
2021
|
-
console.log(
|
|
2381
|
+
console.log(chalk16.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2022
2382
|
console.log("");
|
|
2023
|
-
console.log(` Key ID: ${
|
|
2024
|
-
console.log(` Issuer ID: ${
|
|
2383
|
+
console.log(` Key ID: ${chalk16.bold(result.key_id)}`);
|
|
2384
|
+
console.log(` Issuer ID: ${chalk16.bold(result.issuer_id)}`);
|
|
2025
2385
|
console.log("");
|
|
2026
2386
|
console.log(
|
|
2027
|
-
|
|
2387
|
+
chalk16.yellow(
|
|
2028
2388
|
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2029
2389
|
)
|
|
2030
2390
|
);
|
|
2031
2391
|
}
|
|
2032
2392
|
process.exitCode = 0;
|
|
2033
2393
|
} catch (err) {
|
|
2034
|
-
|
|
2394
|
+
printError5(opts, err);
|
|
2035
2395
|
process.exitCode = 1;
|
|
2036
2396
|
}
|
|
2037
2397
|
}
|
|
2038
2398
|
async function ascKeyList(opts, deps) {
|
|
2039
|
-
const d = deps ??
|
|
2399
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2040
2400
|
try {
|
|
2041
2401
|
const { keys } = await d.listAscKeys();
|
|
2042
2402
|
if (opts.json) {
|
|
@@ -2046,7 +2406,7 @@ async function ascKeyList(opts, deps) {
|
|
|
2046
2406
|
}
|
|
2047
2407
|
process.exitCode = 0;
|
|
2048
2408
|
} catch (err) {
|
|
2049
|
-
|
|
2409
|
+
printError5(opts, err);
|
|
2050
2410
|
process.exitCode = 1;
|
|
2051
2411
|
}
|
|
2052
2412
|
}
|
|
@@ -2054,32 +2414,108 @@ function formatAscKeysHuman(keys) {
|
|
|
2054
2414
|
if (keys.length === 0) {
|
|
2055
2415
|
return "No App Store Connect keys found.\n";
|
|
2056
2416
|
}
|
|
2057
|
-
const
|
|
2417
|
+
const HEADERS4 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
|
|
2058
2418
|
const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, k.created_at ?? ""]);
|
|
2059
|
-
const widths =
|
|
2419
|
+
const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2060
2420
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2061
2421
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2062
|
-
const lines = [
|
|
2422
|
+
const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
|
|
2063
2423
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2064
2424
|
return lines.join("\n") + "\n";
|
|
2065
2425
|
}
|
|
2066
2426
|
async function ascKeyRm(name, opts, deps) {
|
|
2067
|
-
const d = deps ??
|
|
2427
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2068
2428
|
try {
|
|
2069
2429
|
await d.deleteAscKey(name);
|
|
2070
2430
|
if (opts.json) {
|
|
2071
2431
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2072
2432
|
} else {
|
|
2073
|
-
console.log(
|
|
2433
|
+
console.log(chalk16.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2074
2434
|
}
|
|
2075
2435
|
process.exitCode = 0;
|
|
2076
2436
|
} catch (err) {
|
|
2077
|
-
|
|
2437
|
+
printError5(opts, err);
|
|
2438
|
+
process.exitCode = 1;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
async function playKeyAdd(opts, deps) {
|
|
2442
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2443
|
+
if (!opts.file) {
|
|
2444
|
+
throw new UsageError(
|
|
2445
|
+
"--file is required (the service-account JSON from Google Play Console \u2192 API access)"
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
try {
|
|
2449
|
+
const file = await d.readFile(opts.file);
|
|
2450
|
+
const result = await d.addPlayKey({
|
|
2451
|
+
...opts.name !== void 0 ? { name: opts.name } : {},
|
|
2452
|
+
file,
|
|
2453
|
+
filename: basename2(opts.file)
|
|
2454
|
+
});
|
|
2455
|
+
if (opts.json) {
|
|
2456
|
+
console.log(JSON.stringify(result));
|
|
2457
|
+
} else {
|
|
2458
|
+
console.log(chalk16.green(`\u2713 added Google Play key ${result.name}`));
|
|
2459
|
+
console.log("");
|
|
2460
|
+
console.log(` Service account: ${chalk16.bold(result.client_email)}`);
|
|
2461
|
+
console.log("");
|
|
2462
|
+
console.log(
|
|
2463
|
+
chalk16.yellow(
|
|
2464
|
+
" The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2465
|
+
)
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
process.exitCode = 0;
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
printError5(opts, err);
|
|
2471
|
+
process.exitCode = 1;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
async function playKeyList(opts, deps) {
|
|
2475
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2476
|
+
try {
|
|
2477
|
+
const { keys } = await d.listPlayKeys();
|
|
2478
|
+
if (opts.json) {
|
|
2479
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
2480
|
+
} else {
|
|
2481
|
+
process.stdout.write(formatPlayKeysHuman(keys));
|
|
2482
|
+
}
|
|
2483
|
+
process.exitCode = 0;
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
printError5(opts, err);
|
|
2486
|
+
process.exitCode = 1;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function formatPlayKeysHuman(keys) {
|
|
2490
|
+
if (keys.length === 0) {
|
|
2491
|
+
return "No Google Play keys found.\n";
|
|
2492
|
+
}
|
|
2493
|
+
const HEADERS4 = ["NAME", "CLIENT EMAIL", "CREATED"];
|
|
2494
|
+
const rows = keys.map((k) => [k.name, k.client_email, k.created_at ?? ""]);
|
|
2495
|
+
const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2496
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2497
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2498
|
+
const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
|
|
2499
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2500
|
+
return lines.join("\n") + "\n";
|
|
2501
|
+
}
|
|
2502
|
+
async function playKeyRm(name, opts, deps) {
|
|
2503
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2504
|
+
try {
|
|
2505
|
+
await d.deletePlayKey(name);
|
|
2506
|
+
if (opts.json) {
|
|
2507
|
+
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2508
|
+
} else {
|
|
2509
|
+
console.log(chalk16.green(`\u2713 removed Google Play key ${name}`));
|
|
2510
|
+
}
|
|
2511
|
+
process.exitCode = 0;
|
|
2512
|
+
} catch (err) {
|
|
2513
|
+
printError5(opts, err);
|
|
2078
2514
|
process.exitCode = 1;
|
|
2079
2515
|
}
|
|
2080
2516
|
}
|
|
2081
2517
|
async function credentialsList(opts, deps) {
|
|
2082
|
-
const d = deps ??
|
|
2518
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2083
2519
|
try {
|
|
2084
2520
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
2085
2521
|
for (const c of credentials2) {
|
|
@@ -2095,7 +2531,7 @@ async function credentialsList(opts, deps) {
|
|
|
2095
2531
|
}
|
|
2096
2532
|
process.exitCode = 0;
|
|
2097
2533
|
} catch (err) {
|
|
2098
|
-
|
|
2534
|
+
printError5(opts, err);
|
|
2099
2535
|
process.exitCode = 1;
|
|
2100
2536
|
}
|
|
2101
2537
|
}
|
|
@@ -2128,7 +2564,7 @@ function formatCredentialsHuman(credentials2) {
|
|
|
2128
2564
|
if (credentials2.length === 0) {
|
|
2129
2565
|
return "No signing credentials found.\n";
|
|
2130
2566
|
}
|
|
2131
|
-
const
|
|
2567
|
+
const HEADERS4 = ["NAME", "PLATFORM", "SCOPE", "IDENTITY", "EXPIRES"];
|
|
2132
2568
|
const rows = credentials2.map((c) => [
|
|
2133
2569
|
c.name,
|
|
2134
2570
|
c.platform,
|
|
@@ -2136,25 +2572,25 @@ function formatCredentialsHuman(credentials2) {
|
|
|
2136
2572
|
formatIdentity(c),
|
|
2137
2573
|
formatExpires(c)
|
|
2138
2574
|
]);
|
|
2139
|
-
const widths =
|
|
2575
|
+
const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2140
2576
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2141
2577
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2142
|
-
const lines = [
|
|
2578
|
+
const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
|
|
2143
2579
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2144
2580
|
return lines.join("\n") + "\n";
|
|
2145
2581
|
}
|
|
2146
2582
|
async function credentialsRm(name, opts, deps) {
|
|
2147
|
-
const d = deps ??
|
|
2583
|
+
const d = deps ?? defaultDeps6(clientFor2(opts));
|
|
2148
2584
|
try {
|
|
2149
2585
|
await d.deleteCredential({ name, app: opts.app });
|
|
2150
2586
|
if (opts.json) {
|
|
2151
2587
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2152
2588
|
} else {
|
|
2153
|
-
console.log(
|
|
2589
|
+
console.log(chalk16.green(`\u2713 removed ${name}`));
|
|
2154
2590
|
}
|
|
2155
2591
|
process.exitCode = 0;
|
|
2156
2592
|
} catch (err) {
|
|
2157
|
-
|
|
2593
|
+
printError5(opts, err);
|
|
2158
2594
|
process.exitCode = 1;
|
|
2159
2595
|
}
|
|
2160
2596
|
}
|
|
@@ -2162,16 +2598,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
2162
2598
|
const message = err instanceof Error ? err.message : String(err);
|
|
2163
2599
|
if (opts.json) {
|
|
2164
2600
|
console.log(JSON.stringify({ event: "error", message }));
|
|
2165
|
-
console.error(
|
|
2601
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2166
2602
|
return;
|
|
2167
2603
|
}
|
|
2168
|
-
console.error(
|
|
2604
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2169
2605
|
console.error(
|
|
2170
|
-
|
|
2606
|
+
chalk16.red(
|
|
2171
2607
|
`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
2608
|
)
|
|
2173
2609
|
);
|
|
2174
|
-
console.error(
|
|
2610
|
+
console.error(chalk16.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
2175
2611
|
console.error("");
|
|
2176
2612
|
console.error(` Key alias: ${result.key_alias}`);
|
|
2177
2613
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -2182,19 +2618,19 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
2182
2618
|
` Restore the file with: echo '${result.keystore_base64}' | base64 -d > ${result.name}.keystore`
|
|
2183
2619
|
);
|
|
2184
2620
|
}
|
|
2185
|
-
function
|
|
2621
|
+
function printError5(opts, err) {
|
|
2186
2622
|
const message = err instanceof Error ? err.message : String(err);
|
|
2187
2623
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2188
|
-
console.error(
|
|
2624
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2189
2625
|
}
|
|
2190
2626
|
|
|
2191
2627
|
// src/commands/webhooks.ts
|
|
2192
|
-
import
|
|
2628
|
+
import chalk17 from "chalk";
|
|
2193
2629
|
function clientFor3(opts) {
|
|
2194
2630
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2195
2631
|
return new ApiClient(baseUrl, token);
|
|
2196
2632
|
}
|
|
2197
|
-
function
|
|
2633
|
+
function defaultDeps7(client) {
|
|
2198
2634
|
return {
|
|
2199
2635
|
addWebhook: (body) => client.addWebhook(body),
|
|
2200
2636
|
listWebhooks: () => client.listWebhooks(),
|
|
@@ -2211,7 +2647,7 @@ async function webhooksAdd(opts, deps) {
|
|
|
2211
2647
|
if (!opts.url.startsWith("https://")) {
|
|
2212
2648
|
throw new UsageError(`--url must start with https:// (got "${opts.url}")`);
|
|
2213
2649
|
}
|
|
2214
|
-
const d = deps ??
|
|
2650
|
+
const d = deps ?? defaultDeps7(clientFor3(opts));
|
|
2215
2651
|
try {
|
|
2216
2652
|
const result = await d.addWebhook({
|
|
2217
2653
|
url: opts.url,
|
|
@@ -2220,19 +2656,19 @@ async function webhooksAdd(opts, deps) {
|
|
|
2220
2656
|
if (opts.json) {
|
|
2221
2657
|
console.log(JSON.stringify(result));
|
|
2222
2658
|
} else {
|
|
2223
|
-
console.log(
|
|
2659
|
+
console.log(chalk17.green(`\u2713 added ${result.id} ${result.url}`));
|
|
2224
2660
|
console.log("");
|
|
2225
|
-
console.log(` Signing secret: ${
|
|
2661
|
+
console.log(` Signing secret: ${chalk17.bold(result.secret)}`);
|
|
2226
2662
|
if (opts.secret !== void 0) {
|
|
2227
|
-
console.log(
|
|
2663
|
+
console.log(chalk17.yellow(" (This is the secret you provided.)"));
|
|
2228
2664
|
}
|
|
2229
2665
|
console.log(
|
|
2230
|
-
|
|
2666
|
+
chalk17.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
2231
2667
|
);
|
|
2232
2668
|
}
|
|
2233
2669
|
process.exitCode = 0;
|
|
2234
2670
|
} catch (err) {
|
|
2235
|
-
|
|
2671
|
+
printError6(opts, err);
|
|
2236
2672
|
process.exitCode = 1;
|
|
2237
2673
|
}
|
|
2238
2674
|
}
|
|
@@ -2247,7 +2683,7 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
2247
2683
|
if (webhooks2.length === 0) {
|
|
2248
2684
|
return "No webhooks found.\n";
|
|
2249
2685
|
}
|
|
2250
|
-
const
|
|
2686
|
+
const HEADERS4 = ["ID", "URL", "ACTIVE", "LAST DELIVERY", "CREATED"];
|
|
2251
2687
|
const rows = webhooks2.map((w) => [
|
|
2252
2688
|
w.id,
|
|
2253
2689
|
w.url,
|
|
@@ -2255,15 +2691,15 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
2255
2691
|
formatLastDelivery(w.last_delivery),
|
|
2256
2692
|
w.created_at
|
|
2257
2693
|
]);
|
|
2258
|
-
const widths =
|
|
2694
|
+
const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2259
2695
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2260
2696
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2261
|
-
const lines = [
|
|
2697
|
+
const lines = [chalk17.bold(join3(padCells(HEADERS4)))];
|
|
2262
2698
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2263
2699
|
return lines.join("\n") + "\n";
|
|
2264
2700
|
}
|
|
2265
2701
|
async function webhooksList(opts, deps) {
|
|
2266
|
-
const d = deps ??
|
|
2702
|
+
const d = deps ?? defaultDeps7(clientFor3(opts));
|
|
2267
2703
|
try {
|
|
2268
2704
|
const { webhooks: webhooks2 } = await d.listWebhooks();
|
|
2269
2705
|
if (opts.json) {
|
|
@@ -2273,22 +2709,22 @@ async function webhooksList(opts, deps) {
|
|
|
2273
2709
|
}
|
|
2274
2710
|
process.exitCode = 0;
|
|
2275
2711
|
} catch (err) {
|
|
2276
|
-
|
|
2712
|
+
printError6(opts, err);
|
|
2277
2713
|
process.exitCode = 1;
|
|
2278
2714
|
}
|
|
2279
2715
|
}
|
|
2280
2716
|
async function webhooksRm(id, opts, deps) {
|
|
2281
|
-
const d = deps ??
|
|
2717
|
+
const d = deps ?? defaultDeps7(clientFor3(opts));
|
|
2282
2718
|
try {
|
|
2283
2719
|
await d.deleteWebhook(id);
|
|
2284
2720
|
if (opts.json) {
|
|
2285
2721
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
2286
2722
|
} else {
|
|
2287
|
-
console.log(
|
|
2723
|
+
console.log(chalk17.green(`\u2713 removed ${id}`));
|
|
2288
2724
|
}
|
|
2289
2725
|
process.exitCode = 0;
|
|
2290
2726
|
} catch (err) {
|
|
2291
|
-
|
|
2727
|
+
printError6(opts, err);
|
|
2292
2728
|
process.exitCode = 1;
|
|
2293
2729
|
}
|
|
2294
2730
|
}
|
|
@@ -2305,7 +2741,7 @@ function parseUtcTimestamp(iso) {
|
|
|
2305
2741
|
var TEST_POLL_INTERVAL_MS = 3e3;
|
|
2306
2742
|
var TEST_POLL_BUDGET_MS = 6e4;
|
|
2307
2743
|
async function webhooksTest(id, opts, deps) {
|
|
2308
|
-
const d = deps ??
|
|
2744
|
+
const d = deps ?? defaultDeps7(clientFor3(opts));
|
|
2309
2745
|
try {
|
|
2310
2746
|
const startedAt = d.now();
|
|
2311
2747
|
await d.testWebhook(id);
|
|
@@ -2317,13 +2753,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2317
2753
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
2318
2754
|
if (ld.status === "succeeded") {
|
|
2319
2755
|
if (!opts.json) {
|
|
2320
|
-
console.log(
|
|
2756
|
+
console.log(chalk17.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
2321
2757
|
}
|
|
2322
2758
|
process.exitCode = 0;
|
|
2323
2759
|
} else {
|
|
2324
2760
|
if (!opts.json) {
|
|
2325
2761
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
2326
|
-
console.error(
|
|
2762
|
+
console.error(chalk17.red(`\u2717 Failed (${code}) at ${ld.at}`));
|
|
2327
2763
|
}
|
|
2328
2764
|
process.exitCode = 1;
|
|
2329
2765
|
}
|
|
@@ -2332,25 +2768,25 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2332
2768
|
}
|
|
2333
2769
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
2334
2770
|
console.error(
|
|
2335
|
-
|
|
2771
|
+
chalk17.yellow(
|
|
2336
2772
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
2337
2773
|
)
|
|
2338
2774
|
);
|
|
2339
2775
|
process.exitCode = 1;
|
|
2340
2776
|
} catch (err) {
|
|
2341
|
-
|
|
2777
|
+
printError6(opts, err);
|
|
2342
2778
|
process.exitCode = 1;
|
|
2343
2779
|
}
|
|
2344
2780
|
}
|
|
2345
|
-
function
|
|
2781
|
+
function printError6(opts, err) {
|
|
2346
2782
|
const message = err instanceof Error ? err.message : String(err);
|
|
2347
2783
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2348
|
-
console.error(
|
|
2784
|
+
console.error(chalk17.red(`error: ${message}`));
|
|
2349
2785
|
}
|
|
2350
2786
|
|
|
2351
2787
|
// src/lib/formatters/devices.ts
|
|
2352
|
-
import
|
|
2353
|
-
var
|
|
2788
|
+
import chalk18 from "chalk";
|
|
2789
|
+
var HEADERS3 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
2354
2790
|
function formatDevicesHuman(devices) {
|
|
2355
2791
|
if (devices.length === 0) {
|
|
2356
2792
|
return "No devices registered.\n";
|
|
@@ -2363,11 +2799,11 @@ function formatDevicesHuman(devices) {
|
|
|
2363
2799
|
d.os_version,
|
|
2364
2800
|
d.status
|
|
2365
2801
|
]);
|
|
2366
|
-
const widths =
|
|
2802
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2367
2803
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2368
|
-
const lines = [
|
|
2804
|
+
const lines = [chalk18.bold(fmt(HEADERS3))];
|
|
2369
2805
|
for (const row of rows.sort(rowSort)) {
|
|
2370
|
-
const statusColored = row[5] === "available" ?
|
|
2806
|
+
const statusColored = row[5] === "available" ? chalk18.green(row[5]) : row[5] === "busy" ? chalk18.yellow(row[5]) : chalk18.dim(row[5]);
|
|
2371
2807
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
2372
2808
|
}
|
|
2373
2809
|
return lines.join("\n") + "\n";
|
|
@@ -2457,7 +2893,7 @@ import { dirname, join as join2 } from "path";
|
|
|
2457
2893
|
import { fileURLToPath } from "url";
|
|
2458
2894
|
import { spawnSync } from "child_process";
|
|
2459
2895
|
import { confirm } from "@inquirer/prompts";
|
|
2460
|
-
import
|
|
2896
|
+
import chalk19 from "chalk";
|
|
2461
2897
|
function resolveShippedSkillPath(fromDir) {
|
|
2462
2898
|
for (const rel of ["..", join2("..", "..")]) {
|
|
2463
2899
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -2485,23 +2921,23 @@ function registerMcp() {
|
|
|
2485
2921
|
encoding: "utf8"
|
|
2486
2922
|
});
|
|
2487
2923
|
if (res.status === 0) {
|
|
2488
|
-
console.log(
|
|
2924
|
+
console.log(chalk19.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
2489
2925
|
return;
|
|
2490
2926
|
}
|
|
2491
2927
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
2492
2928
|
if (out.includes("already exists")) {
|
|
2493
|
-
console.log(
|
|
2929
|
+
console.log(chalk19.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
2494
2930
|
return;
|
|
2495
2931
|
}
|
|
2496
|
-
console.error(
|
|
2497
|
-
console.error(
|
|
2932
|
+
console.error(chalk19.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
2933
|
+
console.error(chalk19.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
2498
2934
|
}
|
|
2499
2935
|
async function agentSetup(opts) {
|
|
2500
2936
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
2501
2937
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
2502
2938
|
if (!shipped) {
|
|
2503
2939
|
console.error(
|
|
2504
|
-
|
|
2940
|
+
chalk19.red(
|
|
2505
2941
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
2506
2942
|
)
|
|
2507
2943
|
);
|
|
@@ -2510,9 +2946,9 @@ async function agentSetup(opts) {
|
|
|
2510
2946
|
}
|
|
2511
2947
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
2512
2948
|
const result = installSkill(shipped, targetDir);
|
|
2513
|
-
console.log(
|
|
2949
|
+
console.log(chalk19.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
2514
2950
|
console.log(
|
|
2515
|
-
|
|
2951
|
+
chalk19.dim(
|
|
2516
2952
|
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
2953
|
)
|
|
2518
2954
|
);
|
|
@@ -2520,7 +2956,7 @@ async function agentSetup(opts) {
|
|
|
2520
2956
|
if (!claudeCliAvailable()) {
|
|
2521
2957
|
if (opts.mcp === true) {
|
|
2522
2958
|
console.error(
|
|
2523
|
-
|
|
2959
|
+
chalk19.yellow(
|
|
2524
2960
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
2525
2961
|
)
|
|
2526
2962
|
);
|
|
@@ -2534,7 +2970,7 @@ async function agentSetup(opts) {
|
|
|
2534
2970
|
}
|
|
2535
2971
|
console.log(
|
|
2536
2972
|
`
|
|
2537
|
-
Suggested line for your project's ${
|
|
2973
|
+
Suggested line for your project's ${chalk19.bold("CLAUDE.md")} (or ${chalk19.bold(
|
|
2538
2974
|
"AGENTS.md"
|
|
2539
2975
|
)} for other agents):
|
|
2540
2976
|
`
|
|
@@ -2542,7 +2978,7 @@ Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bol
|
|
|
2542
2978
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
2543
2979
|
`);
|
|
2544
2980
|
console.log(
|
|
2545
|
-
|
|
2981
|
+
chalk19.dim(
|
|
2546
2982
|
"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
2983
|
)
|
|
2548
2984
|
);
|
|
@@ -2706,6 +3142,28 @@ program.command("build").description("Run your configured build command and uplo
|
|
|
2706
3142
|
"--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
3143
|
false
|
|
2708
3144
|
).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
|
|
3145
|
+
program.command("submit").description(
|
|
3146
|
+
"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"
|
|
3147
|
+
).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(
|
|
3148
|
+
"--latest",
|
|
3149
|
+
"Submit the newest succeeded production build for --platform (pass exactly one of --build/--latest)",
|
|
3150
|
+
false
|
|
3151
|
+
).option(
|
|
3152
|
+
"--track <track>",
|
|
3153
|
+
"android/androidtv only: Play track \u2014 internal (default), alpha, beta, or production"
|
|
3154
|
+
).option(
|
|
3155
|
+
"--package <name>",
|
|
3156
|
+
"android/androidtv only: package name, for a build not linked to an app"
|
|
3157
|
+
).option(
|
|
3158
|
+
"--asc-key <name>",
|
|
3159
|
+
"ios/tvos only: which stored App Store Connect key to use (default when exactly one \u2014 see `vibeview credentials asc-key add`)"
|
|
3160
|
+
).option(
|
|
3161
|
+
"--release-status <status>",
|
|
3162
|
+
"android/androidtv only: Play release status \u2014 completed (default), draft, or inProgress"
|
|
3163
|
+
).option(
|
|
3164
|
+
"--rollout <fraction>",
|
|
3165
|
+
"android/androidtv only: staged-rollout fraction, 0 < f < 1 (requires --release-status inProgress)"
|
|
3166
|
+
).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
3167
|
var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
|
|
2710
3168
|
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
3169
|
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));
|
|
@@ -2714,6 +3172,13 @@ builds.command("cancel <id>").description("Cancel a queued or running cloud buil
|
|
|
2714
3172
|
builds.command("delete-artifact <id>").description(
|
|
2715
3173
|
"Delete a production build's stored artifact file (irreversible; the build record, logs, and cost stay intact \u2014 only the downloadable file is removed)"
|
|
2716
3174
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((id, opts) => buildsDeleteArtifact(id, opts));
|
|
3175
|
+
var versions = program.command("versions").description(
|
|
3176
|
+
"Manage an app's server-side build-number counters (used by vibeview.json `build.autoIncrement` production builds)"
|
|
3177
|
+
);
|
|
3178
|
+
versions.command("list").description("List an app's build-number counters, one per platform").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "App public id (see `vibeview list-apps`)").option("--json", "Print the raw response as JSON", false).action((opts) => versionsList(opts));
|
|
3179
|
+
versions.command("set <number>").description(
|
|
3180
|
+
"Seed or overwrite one platform's counter \u2014 the next autoIncrement production build gets <number> + 1 (use it to start above a build number already in the store)"
|
|
3181
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "App public id (see `vibeview list-apps`)").option("--platform <platform>", "ios, android, tvos, or androidtv").option("--json", "Print the updated version object as one JSON line", false).action((number, opts) => versionsSet(number, opts));
|
|
2717
3182
|
program.command("whoami").description("Show which account and role this CLI is authenticated as").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the identity as one JSON line", false).action((opts) => whoAmI(opts));
|
|
2718
3183
|
var env = program.command("env").description("Manage cloud build environment variables and secrets");
|
|
2719
3184
|
env.command("list").description("List build variables and secrets (never prints a value)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option("--json", "Print the raw variable list as a JSON array", false).action((opts) => envList(opts));
|
|
@@ -2765,6 +3230,17 @@ ascKey.command("add").description(
|
|
|
2765
3230
|
).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
3231
|
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
3232
|
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));
|
|
3233
|
+
var playKey = credentials.command("play-key").description(
|
|
3234
|
+
"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`"
|
|
3235
|
+
);
|
|
3236
|
+
playKey.command("add").description(
|
|
3237
|
+
"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."
|
|
3238
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
|
|
3239
|
+
"--file <path>",
|
|
3240
|
+
"The service-account JSON key file (Google Play Console \u2192 API access / Google Cloud service accounts)"
|
|
3241
|
+
).option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => playKeyAdd(opts));
|
|
3242
|
+
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));
|
|
3243
|
+
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
3244
|
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
3245
|
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
3246
|
var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
|
|
@@ -2779,7 +3255,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
|
|
|
2779
3255
|
program.command("mcp").description(
|
|
2780
3256
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
2781
3257
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
2782
|
-
const { runMcpServer } = await import("./server-
|
|
3258
|
+
const { runMcpServer } = await import("./server-NR3ZPQF5.js");
|
|
2783
3259
|
await runMcpServer(opts);
|
|
2784
3260
|
});
|
|
2785
3261
|
program.command("agent-setup").description(
|