@videan/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +194 -142
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -733,7 +733,11 @@ var PLANS2 = {
|
|
|
733
733
|
includedUsageCents: 300,
|
|
734
734
|
maxApps: 3,
|
|
735
735
|
maxInstancesPerApp: 1,
|
|
736
|
-
//
|
|
736
|
+
// A *ceiling*, not a promise of free compute: a container task at even
|
|
737
|
+
// 256/512 running all month is ~$16 of usage against the $3 allowance.
|
|
738
|
+
// That is why apps created on this plan default to serverless mode, which
|
|
739
|
+
// meters nothing while idle -- the free tier is "free while it sleeps",
|
|
740
|
+
// and a container app on this plan is a deliberate, metered choice.
|
|
737
741
|
maxCpu: 512,
|
|
738
742
|
maxMemory: 1024,
|
|
739
743
|
maxStorageMb: 1024,
|
|
@@ -1463,39 +1467,79 @@ var rollbackCommand = new Command8("rollback").description("Rollback to the prev
|
|
|
1463
1467
|
|
|
1464
1468
|
// src/commands/secrets.ts
|
|
1465
1469
|
import { Command as Command9 } from "commander";
|
|
1466
|
-
import
|
|
1470
|
+
import chalk11 from "chalk";
|
|
1471
|
+
|
|
1472
|
+
// src/prompt.ts
|
|
1467
1473
|
import inquirer3 from "inquirer";
|
|
1474
|
+
import chalk10 from "chalk";
|
|
1475
|
+
async function prompt(questions, nonInteractiveHint) {
|
|
1476
|
+
if (!process.stdin.isTTY) {
|
|
1477
|
+
console.error(
|
|
1478
|
+
chalk10.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
|
|
1479
|
+
);
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
}
|
|
1482
|
+
try {
|
|
1483
|
+
return await inquirer3.prompt(questions);
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
if (isStreamClosed(err)) {
|
|
1486
|
+
console.error(chalk10.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
|
|
1487
|
+
process.exit(1);
|
|
1488
|
+
}
|
|
1489
|
+
throw err;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
function isStreamClosed(err) {
|
|
1493
|
+
const e = err;
|
|
1494
|
+
return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
|
|
1495
|
+
}
|
|
1496
|
+
function installPromptCrashGuard() {
|
|
1497
|
+
process.on("uncaughtException", (err) => {
|
|
1498
|
+
if (isStreamClosed(err)) {
|
|
1499
|
+
console.error(
|
|
1500
|
+
chalk10.red(
|
|
1501
|
+
"Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
|
|
1502
|
+
)
|
|
1503
|
+
);
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
throw err;
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/commands/secrets.ts
|
|
1468
1511
|
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1469
1512
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1470
1513
|
const apiKey = getApiKey();
|
|
1471
1514
|
requireApiKey(apiKey);
|
|
1472
1515
|
const appId = opts.app || getCurrentApp();
|
|
1473
1516
|
if (!appId) {
|
|
1474
|
-
console.error(
|
|
1517
|
+
console.error(chalk11.red("No app specified."));
|
|
1475
1518
|
process.exit(1);
|
|
1476
1519
|
}
|
|
1477
1520
|
if (!value) {
|
|
1478
|
-
const answers = await
|
|
1479
|
-
{ type: "password", name: "value", message: `Value for ${key}:` }
|
|
1480
|
-
|
|
1521
|
+
const answers = await prompt(
|
|
1522
|
+
[{ type: "password", name: "value", message: `Value for ${key}:` }],
|
|
1523
|
+
`Pass the value as an argument: videan secrets set ${key} <value>`
|
|
1524
|
+
);
|
|
1481
1525
|
value = answers.value;
|
|
1482
1526
|
}
|
|
1483
1527
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1484
1528
|
await client4.secrets.set(appId, key, value);
|
|
1485
|
-
console.log(
|
|
1529
|
+
console.log(chalk11.green(`Secret "${key}" set successfully.`));
|
|
1486
1530
|
});
|
|
1487
1531
|
secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1488
1532
|
const apiKey = getApiKey();
|
|
1489
1533
|
requireApiKey(apiKey);
|
|
1490
1534
|
const appId = opts.app || getCurrentApp();
|
|
1491
1535
|
if (!appId) {
|
|
1492
|
-
console.error(
|
|
1536
|
+
console.error(chalk11.red("No app specified."));
|
|
1493
1537
|
process.exit(1);
|
|
1494
1538
|
}
|
|
1495
1539
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1496
1540
|
const secrets = await client4.secrets.list(appId);
|
|
1497
1541
|
if (secrets.length === 0) {
|
|
1498
|
-
console.log(
|
|
1542
|
+
console.log(chalk11.gray("No secrets configured."));
|
|
1499
1543
|
return;
|
|
1500
1544
|
}
|
|
1501
1545
|
console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
|
|
@@ -1508,24 +1552,23 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
|
|
|
1508
1552
|
requireApiKey(apiKey);
|
|
1509
1553
|
const appId = opts.app || getCurrentApp();
|
|
1510
1554
|
if (!appId) {
|
|
1511
|
-
console.error(
|
|
1555
|
+
console.error(chalk11.red("No app specified."));
|
|
1512
1556
|
process.exit(1);
|
|
1513
1557
|
}
|
|
1514
1558
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1515
1559
|
await client4.secrets.delete(appId, key);
|
|
1516
|
-
console.log(
|
|
1560
|
+
console.log(chalk11.green(`Secret "${key}" removed.`));
|
|
1517
1561
|
});
|
|
1518
1562
|
|
|
1519
1563
|
// src/commands/apps.ts
|
|
1520
1564
|
import { Command as Command10 } from "commander";
|
|
1521
|
-
import
|
|
1522
|
-
import inquirer4 from "inquirer";
|
|
1565
|
+
import chalk12 from "chalk";
|
|
1523
1566
|
var appsCommand = new Command10("apps").description("Manage apps");
|
|
1524
1567
|
function accessOption(value) {
|
|
1525
1568
|
if (value === void 0) return void 0;
|
|
1526
1569
|
const tier = value.trim().toLowerCase();
|
|
1527
1570
|
if (!isVisibility(tier)) {
|
|
1528
|
-
console.error(
|
|
1571
|
+
console.error(chalk12.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
|
|
1529
1572
|
process.exit(1);
|
|
1530
1573
|
}
|
|
1531
1574
|
return tier;
|
|
@@ -1534,7 +1577,7 @@ function modeOption(value) {
|
|
|
1534
1577
|
if (value === void 0) return void 0;
|
|
1535
1578
|
const mode = value.trim().toLowerCase();
|
|
1536
1579
|
if (!isAppMode(mode)) {
|
|
1537
|
-
console.error(
|
|
1580
|
+
console.error(chalk12.red(`Unknown --mode value "${value}". Use one of: ${APP_MODES.join(", ")}`));
|
|
1538
1581
|
process.exit(1);
|
|
1539
1582
|
}
|
|
1540
1583
|
return mode;
|
|
@@ -1550,7 +1593,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1550
1593
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1551
1594
|
const apps = await client4.apps.list(orgId);
|
|
1552
1595
|
if (apps.length === 0) {
|
|
1553
|
-
console.log(
|
|
1596
|
+
console.log(chalk12.gray("No apps yet. Create one with `videan apps create`."));
|
|
1554
1597
|
return;
|
|
1555
1598
|
}
|
|
1556
1599
|
const widths = [20, 24, 10, 44];
|
|
@@ -1572,13 +1615,14 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1572
1615
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1573
1616
|
let name = opts.name;
|
|
1574
1617
|
if (!name) {
|
|
1575
|
-
const answers = await
|
|
1576
|
-
{ type: "input", name: "name", message: "App name:" }
|
|
1577
|
-
|
|
1618
|
+
const answers = await prompt(
|
|
1619
|
+
[{ type: "input", name: "name", message: "App name:" }],
|
|
1620
|
+
"Pass the name: videan apps create --name <name>"
|
|
1621
|
+
);
|
|
1578
1622
|
name = answers.name;
|
|
1579
1623
|
}
|
|
1580
1624
|
if (!name) {
|
|
1581
|
-
console.error(
|
|
1625
|
+
console.error(chalk12.red("An app name is required."));
|
|
1582
1626
|
process.exit(1);
|
|
1583
1627
|
}
|
|
1584
1628
|
const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
|
|
@@ -1595,11 +1639,11 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1595
1639
|
mode
|
|
1596
1640
|
});
|
|
1597
1641
|
setCurrentApp(app.id);
|
|
1598
|
-
console.log(
|
|
1599
|
-
console.log(
|
|
1600
|
-
console.log(
|
|
1601
|
-
console.log(
|
|
1602
|
-
console.log(
|
|
1642
|
+
console.log(chalk12.green(`App "${app.name}" created (${app.id})`));
|
|
1643
|
+
console.log(chalk12.gray(` Mode: ${app.mode ?? "container"}`));
|
|
1644
|
+
console.log(chalk12.gray(` Access: ${describeAccess(app)}`));
|
|
1645
|
+
console.log(chalk12.gray(` URL after first deploy: ${appUrl(app.slug)}`));
|
|
1646
|
+
console.log(chalk12.gray(" Push code with `videan deploy` \u2014 the runtime is auto-detected."));
|
|
1603
1647
|
}
|
|
1604
1648
|
);
|
|
1605
1649
|
appsCommand.command("set").description("Change an app's settings (scale, rate limit, gate, capabilities)").option("-a, --app <id>", "App ID").option("-n, --name <name>", "Rename the app (the URL keeps its original slug)").option("--cpu <units>", "CPU units per instance (256 = 0.25 vCPU)").option("--memory <mib>", "Memory per instance, in MiB").option("--instances <n>", "Number of instances to run").option("--rate-limit <rpm>", "Requests per minute per client").option("--no-rate-limit", "Remove the rate limit").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")}`).option("--public", "Serve without the platform login gate (same as --access public)").option("--private", "Require platform login (leaves the tier alone if it is already gated)").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--stack <label>", "Group this app with its siblings under a shared label").option("--no-stack", "Remove the app from its stack").option("--mode <mode>", `How the app runs: ${APP_MODES.join(" | ")} (applies on the next deploy or restart)`).option("--json", "Print the raw response").action(
|
|
@@ -1608,16 +1652,16 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1608
1652
|
requireApiKey(apiKey);
|
|
1609
1653
|
const appId = opts.app || getCurrentApp();
|
|
1610
1654
|
if (!appId) {
|
|
1611
|
-
console.error(
|
|
1655
|
+
console.error(chalk12.red("No app specified. Use --app or link an app."));
|
|
1612
1656
|
process.exit(1);
|
|
1613
1657
|
}
|
|
1614
1658
|
if (opts.public && opts.private) {
|
|
1615
|
-
console.error(
|
|
1659
|
+
console.error(chalk12.red("--public and --private contradict each other."));
|
|
1616
1660
|
process.exit(1);
|
|
1617
1661
|
}
|
|
1618
1662
|
const access = accessOption(opts.access);
|
|
1619
1663
|
if (access && (opts.public || opts.private)) {
|
|
1620
|
-
console.error(
|
|
1664
|
+
console.error(chalk12.red("--access already says who may open the app; drop --public/--private."));
|
|
1621
1665
|
process.exit(1);
|
|
1622
1666
|
}
|
|
1623
1667
|
const mode = modeOption(opts.mode);
|
|
@@ -1642,7 +1686,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1642
1686
|
if (opts.stack === false) changes.stack = null;
|
|
1643
1687
|
else if (typeof opts.stack === "string") changes.stack = opts.stack;
|
|
1644
1688
|
if (Object.keys(changes).length === 0) {
|
|
1645
|
-
console.error(
|
|
1689
|
+
console.error(chalk12.red("Nothing to change. Pass at least one option."));
|
|
1646
1690
|
process.exit(1);
|
|
1647
1691
|
}
|
|
1648
1692
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1651,7 +1695,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1651
1695
|
console.log(JSON.stringify(app, null, 2));
|
|
1652
1696
|
return;
|
|
1653
1697
|
}
|
|
1654
|
-
console.log(
|
|
1698
|
+
console.log(chalk12.green(`Updated ${app.name} (${app.id})`));
|
|
1655
1699
|
console.log(` Mode: ${app.mode ?? "container"}`);
|
|
1656
1700
|
console.log(` Access: ${describeAccess(app)}`);
|
|
1657
1701
|
console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
|
|
@@ -1660,7 +1704,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1660
1704
|
);
|
|
1661
1705
|
if (app.restartRequired) {
|
|
1662
1706
|
console.log(
|
|
1663
|
-
|
|
1707
|
+
chalk12.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
|
|
1664
1708
|
);
|
|
1665
1709
|
}
|
|
1666
1710
|
}
|
|
@@ -1668,37 +1712,42 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1668
1712
|
function intOption(flag, raw) {
|
|
1669
1713
|
const n = Number(raw);
|
|
1670
1714
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1671
|
-
console.error(
|
|
1715
|
+
console.error(chalk12.red(`${flag} expects a positive whole number, got "${raw}".`));
|
|
1672
1716
|
process.exit(1);
|
|
1673
1717
|
}
|
|
1674
1718
|
return n;
|
|
1675
1719
|
}
|
|
1676
|
-
appsCommand.command("delete <id>").description("Delete an app and all its resources").action(async (id) => {
|
|
1720
|
+
appsCommand.command("delete <id>").description("Delete an app and all its resources").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
|
|
1677
1721
|
const apiKey = getApiKey();
|
|
1678
1722
|
requireApiKey(apiKey);
|
|
1679
|
-
|
|
1680
|
-
{
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1723
|
+
if (!opts.yes) {
|
|
1724
|
+
const { confirm } = await prompt(
|
|
1725
|
+
[
|
|
1726
|
+
{
|
|
1727
|
+
type: "confirm",
|
|
1728
|
+
name: "confirm",
|
|
1729
|
+
message: `Delete app ${id}? This stops the app and destroys its database, secrets and snapshots.`,
|
|
1730
|
+
default: false
|
|
1731
|
+
}
|
|
1732
|
+
],
|
|
1733
|
+
"Confirm with a flag: videan apps delete <id> --yes"
|
|
1734
|
+
);
|
|
1735
|
+
if (!confirm) return;
|
|
1736
|
+
}
|
|
1688
1737
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1689
1738
|
const { warnings } = await client4.apps.delete(id);
|
|
1690
|
-
console.log(
|
|
1739
|
+
console.log(chalk12.green("App deleted."));
|
|
1691
1740
|
if (warnings.length > 0) {
|
|
1692
|
-
console.log(
|
|
1741
|
+
console.log(chalk12.yellow("\nSome resources could not be released:"));
|
|
1693
1742
|
for (const w of warnings) {
|
|
1694
|
-
console.log(
|
|
1743
|
+
console.log(chalk12.yellow(` ${w.step}: ${w.error || "unknown error"}`));
|
|
1695
1744
|
}
|
|
1696
1745
|
}
|
|
1697
1746
|
});
|
|
1698
1747
|
|
|
1699
1748
|
// src/commands/status.ts
|
|
1700
1749
|
import { Command as Command11 } from "commander";
|
|
1701
|
-
import
|
|
1750
|
+
import chalk13 from "chalk";
|
|
1702
1751
|
function accessLine(app) {
|
|
1703
1752
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1704
1753
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
@@ -1708,42 +1757,41 @@ var statusCommand = new Command11("status").description("Show current app status
|
|
|
1708
1757
|
requireApiKey(apiKey);
|
|
1709
1758
|
const appId = opts.app || getCurrentApp();
|
|
1710
1759
|
if (!appId) {
|
|
1711
|
-
console.error(
|
|
1760
|
+
console.error(chalk13.red("No app specified. Use --app or link an app."));
|
|
1712
1761
|
process.exit(1);
|
|
1713
1762
|
}
|
|
1714
1763
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1715
1764
|
const app = await client4.apps.get(appId);
|
|
1716
1765
|
const health = await client4.apps.health(appId);
|
|
1717
|
-
console.log(
|
|
1766
|
+
console.log(chalk13.bold(app.name));
|
|
1718
1767
|
console.log(` ID: ${app.id}`);
|
|
1719
1768
|
console.log(` Created: ${app.createdAt}`);
|
|
1720
1769
|
console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
|
|
1721
1770
|
console.log(` Access: ${accessLine(app)}`);
|
|
1722
1771
|
console.log(` State: ${formatStatus(health.state)}`);
|
|
1723
|
-
console.log(` Healthy: ${health.healthy ?
|
|
1724
|
-
console.log(` URL: ${
|
|
1772
|
+
console.log(` Healthy: ${health.healthy ? chalk13.green("yes") : chalk13.red("no")}`);
|
|
1773
|
+
console.log(` URL: ${chalk13.cyan(health.url || appUrl(app.slug))}`);
|
|
1725
1774
|
const task = health.task;
|
|
1726
1775
|
if (task) {
|
|
1727
1776
|
if (task.exit_code !== void 0 && task.exit_code !== null) {
|
|
1728
1777
|
const code = String(task.exit_code);
|
|
1729
|
-
console.log(` Exit code: ${task.exit_code === 0 ? code :
|
|
1778
|
+
console.log(` Exit code: ${task.exit_code === 0 ? code : chalk13.red(code)}`);
|
|
1730
1779
|
}
|
|
1731
1780
|
if (task.stopped_reason) {
|
|
1732
|
-
console.log(` Stopped: ${
|
|
1781
|
+
console.log(` Stopped: ${chalk13.yellow(task.stopped_reason)}`);
|
|
1733
1782
|
}
|
|
1734
1783
|
if (task.container_reason) {
|
|
1735
|
-
console.log(` Detail: ${
|
|
1784
|
+
console.log(` Detail: ${chalk13.yellow(task.container_reason)}`);
|
|
1736
1785
|
}
|
|
1737
1786
|
}
|
|
1738
1787
|
if (!health.healthy) {
|
|
1739
|
-
console.log(
|
|
1788
|
+
console.log(chalk13.gray("\nRun `videan logs` to see why."));
|
|
1740
1789
|
}
|
|
1741
1790
|
});
|
|
1742
1791
|
|
|
1743
1792
|
// src/commands/keys.ts
|
|
1744
1793
|
import { Command as Command12 } from "commander";
|
|
1745
|
-
import
|
|
1746
|
-
import inquirer5 from "inquirer";
|
|
1794
|
+
import chalk14 from "chalk";
|
|
1747
1795
|
var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
|
|
1748
1796
|
keysCommand.command("create <name>").description("Create an API key").option("-o, --org <id>", "Organization ID").option("-e, --expires <days>", "Expire the key after N days").option(
|
|
1749
1797
|
"--app <id>",
|
|
@@ -1756,7 +1804,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1756
1804
|
if (opts.expires !== void 0) {
|
|
1757
1805
|
expiresInDays = Number(opts.expires);
|
|
1758
1806
|
if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
|
|
1759
|
-
console.error(
|
|
1807
|
+
console.error(chalk14.red("--expires must be a whole number of days, at least 1."));
|
|
1760
1808
|
process.exit(1);
|
|
1761
1809
|
}
|
|
1762
1810
|
}
|
|
@@ -1767,22 +1815,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1767
1815
|
expiresInDays,
|
|
1768
1816
|
appId: opts.app
|
|
1769
1817
|
});
|
|
1770
|
-
console.log(
|
|
1818
|
+
console.log(chalk14.green(`Created key "${key.name}" (${key.id})`));
|
|
1771
1819
|
console.log(`
|
|
1772
|
-
${
|
|
1820
|
+
${chalk14.bold(key.key)}
|
|
1773
1821
|
`);
|
|
1774
|
-
console.log(
|
|
1822
|
+
console.log(chalk14.yellow("Copy it now. It cannot be shown again."));
|
|
1775
1823
|
if (key.appId) {
|
|
1776
1824
|
console.log(
|
|
1777
|
-
|
|
1825
|
+
chalk14.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
|
|
1778
1826
|
);
|
|
1779
1827
|
} else {
|
|
1780
1828
|
console.log(
|
|
1781
|
-
|
|
1829
|
+
chalk14.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
|
|
1782
1830
|
);
|
|
1783
1831
|
}
|
|
1784
1832
|
if (key.expiresAt) {
|
|
1785
|
-
console.log(
|
|
1833
|
+
console.log(chalk14.gray(`Expires ${key.expiresAt}`));
|
|
1786
1834
|
}
|
|
1787
1835
|
});
|
|
1788
1836
|
keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
@@ -1792,13 +1840,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1792
1840
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1793
1841
|
const keys = await client4.apiKeys.list(orgId);
|
|
1794
1842
|
if (keys.length === 0) {
|
|
1795
|
-
console.log(
|
|
1843
|
+
console.log(chalk14.gray("No API keys. Create one with `videan keys create <name>`."));
|
|
1796
1844
|
return;
|
|
1797
1845
|
}
|
|
1798
1846
|
const widths = [24, 24, 16, 22, 12, 24];
|
|
1799
1847
|
console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
|
|
1800
1848
|
for (const key of keys) {
|
|
1801
|
-
const state = key.revokedAt ?
|
|
1849
|
+
const state = key.revokedAt ? chalk14.red("revoked") : key.expiresAt && new Date(key.expiresAt) < /* @__PURE__ */ new Date() ? chalk14.yellow("expired") : chalk14.green("active");
|
|
1802
1850
|
console.log(formatRow(
|
|
1803
1851
|
[
|
|
1804
1852
|
key.name,
|
|
@@ -1812,21 +1860,24 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1812
1860
|
));
|
|
1813
1861
|
}
|
|
1814
1862
|
});
|
|
1815
|
-
keysCommand.command("revoke <id>").description("Revoke an API key").action(async (id) => {
|
|
1863
|
+
keysCommand.command("revoke <id>").description("Revoke an API key").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
|
|
1816
1864
|
const apiKey = getApiKey();
|
|
1817
1865
|
requireApiKey(apiKey);
|
|
1818
|
-
|
|
1819
|
-
{
|
|
1820
|
-
|
|
1821
|
-
|
|
1866
|
+
if (!opts.yes) {
|
|
1867
|
+
const { confirm } = await prompt(
|
|
1868
|
+
[{ type: "confirm", name: "confirm", message: `Revoke key ${id}?`, default: false }],
|
|
1869
|
+
"Confirm with a flag: videan keys revoke <id> --yes"
|
|
1870
|
+
);
|
|
1871
|
+
if (!confirm) return;
|
|
1872
|
+
}
|
|
1822
1873
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1823
1874
|
await client4.apiKeys.revoke(id);
|
|
1824
|
-
console.log(
|
|
1875
|
+
console.log(chalk14.green("Key revoked."));
|
|
1825
1876
|
});
|
|
1826
1877
|
|
|
1827
1878
|
// src/commands/orgs.ts
|
|
1828
1879
|
import { Command as Command13 } from "commander";
|
|
1829
|
-
import
|
|
1880
|
+
import chalk15 from "chalk";
|
|
1830
1881
|
var orgsCommand = new Command13("orgs").description("Manage organizations");
|
|
1831
1882
|
orgsCommand.command("list").description("List your organizations").action(async () => {
|
|
1832
1883
|
const apiKey = getApiKey();
|
|
@@ -1835,14 +1886,14 @@ orgsCommand.command("list").description("List your organizations").action(async
|
|
|
1835
1886
|
const orgs = await client4.orgs.list();
|
|
1836
1887
|
const current = getDefaultOrg();
|
|
1837
1888
|
if (orgs.length === 0) {
|
|
1838
|
-
console.log(
|
|
1889
|
+
console.log(chalk15.gray("No organizations. Create one with `videan orgs create <name>`."));
|
|
1839
1890
|
return;
|
|
1840
1891
|
}
|
|
1841
1892
|
const widths = [24, 24, 10, 8];
|
|
1842
1893
|
console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
|
|
1843
1894
|
for (const org of orgs) {
|
|
1844
1895
|
console.log(formatRow(
|
|
1845
|
-
[org.name, org.id, org.plan, org.id === current ?
|
|
1896
|
+
[org.name, org.id, org.plan, org.id === current ? chalk15.green("default") : ""],
|
|
1846
1897
|
widths
|
|
1847
1898
|
));
|
|
1848
1899
|
}
|
|
@@ -1853,7 +1904,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
|
|
|
1853
1904
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1854
1905
|
const org = await client4.orgs.create(name);
|
|
1855
1906
|
if (!getDefaultOrg()) setDefaultOrg(org.id);
|
|
1856
|
-
console.log(
|
|
1907
|
+
console.log(chalk15.green(`Organization "${org.name}" created (${org.id})`));
|
|
1857
1908
|
});
|
|
1858
1909
|
orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
|
|
1859
1910
|
const apiKey = getApiKey();
|
|
@@ -1861,7 +1912,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
|
|
|
1861
1912
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1862
1913
|
const org = await client4.orgs.get(id);
|
|
1863
1914
|
setDefaultOrg(org.id);
|
|
1864
|
-
console.log(
|
|
1915
|
+
console.log(chalk15.green(`Default organization set to "${org.name}".`));
|
|
1865
1916
|
});
|
|
1866
1917
|
orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
1867
1918
|
const apiKey = getApiKey();
|
|
@@ -1884,12 +1935,12 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1884
1935
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1885
1936
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1886
1937
|
await client4.orgs.invite(orgId, email);
|
|
1887
|
-
console.log(
|
|
1938
|
+
console.log(chalk15.green(`Invited ${email}.`));
|
|
1888
1939
|
});
|
|
1889
1940
|
|
|
1890
1941
|
// src/commands/storage.ts
|
|
1891
1942
|
import { Command as Command14 } from "commander";
|
|
1892
|
-
import
|
|
1943
|
+
import chalk16 from "chalk";
|
|
1893
1944
|
import fs5 from "fs-extra";
|
|
1894
1945
|
import path5 from "node:path";
|
|
1895
1946
|
var storageCommand = new Command14("storage").description("Store files for an app");
|
|
@@ -1901,7 +1952,7 @@ function client() {
|
|
|
1901
1952
|
function requireApp(opt) {
|
|
1902
1953
|
const appId = opt || getCurrentApp();
|
|
1903
1954
|
if (!appId) {
|
|
1904
|
-
console.error(
|
|
1955
|
+
console.error(chalk16.red("No app specified. Use --app or link an app."));
|
|
1905
1956
|
process.exit(1);
|
|
1906
1957
|
}
|
|
1907
1958
|
return appId;
|
|
@@ -1924,7 +1975,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1924
1975
|
return;
|
|
1925
1976
|
}
|
|
1926
1977
|
if (objects.length === 0) {
|
|
1927
|
-
console.log(
|
|
1978
|
+
console.log(chalk16.gray("No objects stored. Upload one with `videan storage put <file>`."));
|
|
1928
1979
|
return;
|
|
1929
1980
|
}
|
|
1930
1981
|
const widths = [52, 12, 24];
|
|
@@ -1933,13 +1984,13 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1933
1984
|
console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
|
|
1934
1985
|
}
|
|
1935
1986
|
const total = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1936
|
-
console.log(
|
|
1987
|
+
console.log(chalk16.dim(`
|
|
1937
1988
|
${objects.length} object(s), ${human(total)}`));
|
|
1938
1989
|
});
|
|
1939
1990
|
storageCommand.command("put <file> [key]").description("Upload a file (key defaults to the file's name)").option("-a, --app <id>", "App ID").option("-t, --type <mime>", "Content type").action(async (file, key, opts) => {
|
|
1940
1991
|
const appId = requireApp(opts.app);
|
|
1941
1992
|
if (!await fs5.pathExists(file)) {
|
|
1942
|
-
console.error(
|
|
1993
|
+
console.error(chalk16.red(`No such file: ${file}`));
|
|
1943
1994
|
process.exit(1);
|
|
1944
1995
|
}
|
|
1945
1996
|
const body = await fs5.readFile(file);
|
|
@@ -1948,27 +1999,27 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
|
|
|
1948
1999
|
contentType: opts.type
|
|
1949
2000
|
});
|
|
1950
2001
|
console.log(
|
|
1951
|
-
|
|
2002
|
+
chalk16.green(`Uploaded ${objectKey}`) + chalk16.dim(` (${human(body.length)})`)
|
|
1952
2003
|
);
|
|
1953
|
-
if (stored.etag) console.log(
|
|
2004
|
+
if (stored.etag) console.log(chalk16.dim(`etag ${stored.etag}`));
|
|
1954
2005
|
});
|
|
1955
2006
|
storageCommand.command("get <key> [file]").description("Download an object (defaults to the key's basename)").option("-a, --app <id>", "App ID").action(async (key, file, opts) => {
|
|
1956
2007
|
const appId = requireApp(opts.app);
|
|
1957
2008
|
const bytes = await client().storage.download(appId, key);
|
|
1958
2009
|
const dest = file || path5.basename(key);
|
|
1959
2010
|
await fs5.writeFile(dest, bytes);
|
|
1960
|
-
console.log(
|
|
2011
|
+
console.log(chalk16.green(`Wrote ${dest}`) + chalk16.dim(` (${human(bytes.length)})`));
|
|
1961
2012
|
});
|
|
1962
2013
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
1963
2014
|
const appId = requireApp(opts.app);
|
|
1964
2015
|
await client().storage.delete(appId, key);
|
|
1965
|
-
console.log(
|
|
2016
|
+
console.log(chalk16.green(`Deleted ${key}`));
|
|
1966
2017
|
});
|
|
1967
2018
|
storageCommand.command("url <key>").description("Print a temporary link to an object").option("-a, --app <id>", "App ID").option("-e, --expires <seconds>", "Lifetime in seconds (30-3600)", "900").option("--upload", "Sign an upload (PUT) URL instead of a download").option("-d, --download <filename>", "Serve as a download with this filename").action(async (key, opts) => {
|
|
1968
2019
|
const appId = requireApp(opts.app);
|
|
1969
2020
|
const expiresIn = Number(opts.expires);
|
|
1970
2021
|
if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
|
|
1971
|
-
console.error(
|
|
2022
|
+
console.error(chalk16.red("--expires must be a whole number of seconds between 30 and 3600."));
|
|
1972
2023
|
process.exit(1);
|
|
1973
2024
|
}
|
|
1974
2025
|
const signed = await client().storage.presign(appId, key, {
|
|
@@ -1978,7 +2029,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
|
|
|
1978
2029
|
});
|
|
1979
2030
|
console.log(signed.url);
|
|
1980
2031
|
console.error(
|
|
1981
|
-
|
|
2032
|
+
chalk16.yellow(
|
|
1982
2033
|
`This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
|
|
1983
2034
|
)
|
|
1984
2035
|
);
|
|
@@ -1991,7 +2042,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1991
2042
|
|
|
1992
2043
|
// src/commands/email.ts
|
|
1993
2044
|
import { Command as Command15 } from "commander";
|
|
1994
|
-
import
|
|
2045
|
+
import chalk17 from "chalk";
|
|
1995
2046
|
import fs6 from "fs-extra";
|
|
1996
2047
|
var emailCommand = new Command15("email").description("Send email from an app");
|
|
1997
2048
|
function client2() {
|
|
@@ -2002,7 +2053,7 @@ function client2() {
|
|
|
2002
2053
|
function requireApp2(opt) {
|
|
2003
2054
|
const appId = opt || getCurrentApp();
|
|
2004
2055
|
if (!appId) {
|
|
2005
|
-
console.error(
|
|
2056
|
+
console.error(chalk17.red("No app specified. Use --app or link an app."));
|
|
2006
2057
|
process.exit(1);
|
|
2007
2058
|
}
|
|
2008
2059
|
return appId;
|
|
@@ -2018,7 +2069,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
2018
2069
|
else text = contents;
|
|
2019
2070
|
}
|
|
2020
2071
|
if (!text && !html) {
|
|
2021
|
-
console.error(
|
|
2072
|
+
console.error(chalk17.red("Nothing to send. Pass --text, --html or --body-file."));
|
|
2022
2073
|
process.exit(1);
|
|
2023
2074
|
}
|
|
2024
2075
|
const sent = await client2().email.send(appId, {
|
|
@@ -2032,12 +2083,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
2032
2083
|
console.log(JSON.stringify(sent, null, 2));
|
|
2033
2084
|
return;
|
|
2034
2085
|
}
|
|
2035
|
-
console.log(
|
|
2086
|
+
console.log(chalk17.green("Sent"));
|
|
2036
2087
|
console.log(` From: ${sent.from}`);
|
|
2037
2088
|
console.log(` To: ${sent.to.join(", ")}`);
|
|
2038
2089
|
console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
|
|
2039
2090
|
console.log(
|
|
2040
|
-
|
|
2091
|
+
chalk17.dim(
|
|
2041
2092
|
` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
|
|
2042
2093
|
)
|
|
2043
2094
|
);
|
|
@@ -2052,12 +2103,12 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
2052
2103
|
console.log(`Sent today: ${quota.sent}`);
|
|
2053
2104
|
console.log(`Daily quota: ${quota.quota}`);
|
|
2054
2105
|
console.log(`Remaining: ${quota.remaining}`);
|
|
2055
|
-
console.log(
|
|
2106
|
+
console.log(chalk17.dim(`Resets at: ${quota.resetsAt}`));
|
|
2056
2107
|
});
|
|
2057
2108
|
|
|
2058
2109
|
// src/commands/jobs.ts
|
|
2059
2110
|
import { Command as Command16 } from "commander";
|
|
2060
|
-
import
|
|
2111
|
+
import chalk18 from "chalk";
|
|
2061
2112
|
import fs7 from "fs-extra";
|
|
2062
2113
|
var jobsCommand = new Command16("jobs").description("Enqueue and inspect background jobs");
|
|
2063
2114
|
function client3() {
|
|
@@ -2068,7 +2119,7 @@ function client3() {
|
|
|
2068
2119
|
function requireApp3(opt) {
|
|
2069
2120
|
const appId = opt || getCurrentApp();
|
|
2070
2121
|
if (!appId) {
|
|
2071
|
-
console.error(
|
|
2122
|
+
console.error(chalk18.red("No app specified. Use --app or run `videan init` first."));
|
|
2072
2123
|
process.exit(1);
|
|
2073
2124
|
}
|
|
2074
2125
|
return appId;
|
|
@@ -2076,20 +2127,20 @@ function requireApp3(opt) {
|
|
|
2076
2127
|
function intOption2(flag, raw) {
|
|
2077
2128
|
const n = Number(raw);
|
|
2078
2129
|
if (!Number.isInteger(n) || n < 0) {
|
|
2079
|
-
console.error(
|
|
2130
|
+
console.error(chalk18.red(`${flag} expects a whole number, got "${raw}".`));
|
|
2080
2131
|
process.exit(1);
|
|
2081
2132
|
}
|
|
2082
2133
|
return n;
|
|
2083
2134
|
}
|
|
2084
2135
|
var STATUS_COLOR = {
|
|
2085
|
-
queued:
|
|
2086
|
-
running:
|
|
2087
|
-
succeeded:
|
|
2088
|
-
dead:
|
|
2089
|
-
cancelled:
|
|
2136
|
+
queued: chalk18.yellow,
|
|
2137
|
+
running: chalk18.cyan,
|
|
2138
|
+
succeeded: chalk18.green,
|
|
2139
|
+
dead: chalk18.red,
|
|
2140
|
+
cancelled: chalk18.gray
|
|
2090
2141
|
};
|
|
2091
2142
|
function statusLabel(status) {
|
|
2092
|
-
return (STATUS_COLOR[status] ??
|
|
2143
|
+
return (STATUS_COLOR[status] ?? chalk18.white)(status);
|
|
2093
2144
|
}
|
|
2094
2145
|
jobsCommand.command("enqueue").description("Queue a job for the app to run").option("-a, --app <id>", "App ID").option("-q, --queue <name>", "Queue name (default: default)").option("-p, --path <path>", "Path on the app to POST to (default: /jobs)").option("-d, --data <json>", "JSON payload").option("-f, --file <path>", "Read the JSON payload from a file").option("--delay <seconds>", "Run no earlier than this many seconds from now").option("--attempts <n>", "Attempts before the job is given up on (default: 3)").option("--timeout <seconds>", "How long the handler gets per attempt (default: 60)").option("--key <key>", "Idempotency key -- a repeat returns the same job").option("--json", "Print the raw response").action(async (opts) => {
|
|
2095
2146
|
const appId = requireApp3(opts.app);
|
|
@@ -2099,7 +2150,7 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2099
2150
|
try {
|
|
2100
2151
|
payload = JSON.parse(raw);
|
|
2101
2152
|
} catch (e) {
|
|
2102
|
-
console.error(
|
|
2153
|
+
console.error(chalk18.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
|
|
2103
2154
|
process.exit(1);
|
|
2104
2155
|
}
|
|
2105
2156
|
}
|
|
@@ -2117,13 +2168,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2117
2168
|
return;
|
|
2118
2169
|
}
|
|
2119
2170
|
if (job.deduplicated) {
|
|
2120
|
-
console.log(
|
|
2171
|
+
console.log(chalk18.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
|
|
2121
2172
|
} else {
|
|
2122
|
-
console.log(
|
|
2173
|
+
console.log(chalk18.green(`Queued ${job.id}`));
|
|
2123
2174
|
}
|
|
2124
|
-
console.log(
|
|
2175
|
+
console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2125
2176
|
if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
|
|
2126
|
-
console.log(
|
|
2177
|
+
console.log(chalk18.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
|
|
2127
2178
|
}
|
|
2128
2179
|
});
|
|
2129
2180
|
jobsCommand.command("list").description("List jobs, newest first").option("-a, --app <id>", "App ID").option("-s, --status <status>", "queued, running, succeeded, dead or cancelled").option("-q, --queue <name>", "Only this queue").option("-n, --limit <n>", "How many to show (default: 20)").option("--json", "Print the raw response").action(async (opts) => {
|
|
@@ -2138,17 +2189,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
|
|
|
2138
2189
|
return;
|
|
2139
2190
|
}
|
|
2140
2191
|
const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
|
|
2141
|
-
console.log(depth ||
|
|
2192
|
+
console.log(depth || chalk18.gray("No jobs yet."));
|
|
2142
2193
|
if (jobs.length === 0) return;
|
|
2143
2194
|
console.log();
|
|
2144
2195
|
for (const job of jobs) {
|
|
2145
|
-
const attempts = job.attempts > 0 ?
|
|
2196
|
+
const attempts = job.attempts > 0 ? chalk18.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
|
|
2146
2197
|
console.log(
|
|
2147
|
-
`${statusLabel(job.status).padEnd(20)} ${
|
|
2198
|
+
`${statusLabel(job.status).padEnd(20)} ${chalk18.white(job.id)} ${chalk18.gray(
|
|
2148
2199
|
`${job.queue} ${job.path}`
|
|
2149
2200
|
)}${attempts}`
|
|
2150
2201
|
);
|
|
2151
|
-
if (job.error) console.log(
|
|
2202
|
+
if (job.error) console.log(chalk18.red(` ${job.error}`));
|
|
2152
2203
|
}
|
|
2153
2204
|
});
|
|
2154
2205
|
jobsCommand.command("get <job-id>").description("Show one job in full").option("-a, --app <id>", "App ID").option("--json", "Print the raw response").action(async (jobId, opts) => {
|
|
@@ -2157,17 +2208,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2157
2208
|
console.log(JSON.stringify(job, null, 2));
|
|
2158
2209
|
return;
|
|
2159
2210
|
}
|
|
2160
|
-
console.log(`${
|
|
2161
|
-
console.log(
|
|
2162
|
-
console.log(
|
|
2163
|
-
if (job.statusCode) console.log(
|
|
2164
|
-
if (job.durationMs) console.log(
|
|
2165
|
-
if (job.error) console.log(
|
|
2211
|
+
console.log(`${chalk18.white(job.id)} ${statusLabel(job.status)}`);
|
|
2212
|
+
console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2213
|
+
console.log(chalk18.gray(` Attempts: ${job.attempts}/${job.maxAttempts}`));
|
|
2214
|
+
if (job.statusCode) console.log(chalk18.gray(` Last response: HTTP ${job.statusCode}`));
|
|
2215
|
+
if (job.durationMs) console.log(chalk18.gray(` Took: ${job.durationMs}ms`));
|
|
2216
|
+
if (job.error) console.log(chalk18.red(` Error: ${job.error}`));
|
|
2166
2217
|
if (job.status === "queued" && job.attempts > 0) {
|
|
2167
|
-
console.log(
|
|
2218
|
+
console.log(chalk18.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
|
|
2168
2219
|
}
|
|
2169
2220
|
if (job.payload !== null && job.payload !== void 0) {
|
|
2170
|
-
console.log(
|
|
2221
|
+
console.log(chalk18.gray(" Payload:"));
|
|
2171
2222
|
console.log(
|
|
2172
2223
|
JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
|
|
2173
2224
|
);
|
|
@@ -2175,16 +2226,16 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2175
2226
|
});
|
|
2176
2227
|
jobsCommand.command("retry <job-id>").description("Run a dead job again, from a clean attempt count").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
|
|
2177
2228
|
const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
|
|
2178
|
-
console.log(
|
|
2229
|
+
console.log(chalk18.green(`Requeued ${job.id}`));
|
|
2179
2230
|
});
|
|
2180
2231
|
jobsCommand.command("cancel <job-id>").description("Cancel a job that has not started yet").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
|
|
2181
2232
|
const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
|
|
2182
|
-
console.log(
|
|
2233
|
+
console.log(chalk18.green(`Cancelled ${job.id}`));
|
|
2183
2234
|
});
|
|
2184
2235
|
|
|
2185
2236
|
// src/commands/usage.ts
|
|
2186
2237
|
import { Command as Command17 } from "commander";
|
|
2187
|
-
import
|
|
2238
|
+
import chalk19 from "chalk";
|
|
2188
2239
|
var usageCommand = new Command17("usage").description("Metered usage and cost for the current month").option("-o, --org <id>", "Organization ID").option("-a, --app <id>", "Show one app's share instead of the whole org").option("-p, --period <YYYY-MM>", "Month to report on (default: the month in progress)").option("--json", "Print the raw response").action(async (opts) => {
|
|
2189
2240
|
const apiKey = getApiKey();
|
|
2190
2241
|
requireApiKey(apiKey);
|
|
@@ -2193,61 +2244,61 @@ var usageCommand = new Command17("usage").description("Metered usage and cost fo
|
|
|
2193
2244
|
const appId = opts.app || getCurrentApp();
|
|
2194
2245
|
const report2 = await client4.apps.usage(appId, opts.period);
|
|
2195
2246
|
if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
|
|
2196
|
-
console.log(
|
|
2247
|
+
console.log(chalk19.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
|
|
2197
2248
|
console.log();
|
|
2198
2249
|
printMetrics(report2.metrics);
|
|
2199
2250
|
console.log();
|
|
2200
2251
|
for (const line of report2.lines) {
|
|
2201
|
-
console.log(` ${
|
|
2252
|
+
console.log(` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)}`);
|
|
2202
2253
|
}
|
|
2203
|
-
console.log(` ${
|
|
2254
|
+
console.log(` ${chalk19.bold("Share of the bill".padEnd(22))} ${chalk19.bold(report2.cost)}`);
|
|
2204
2255
|
console.log();
|
|
2205
2256
|
console.log(
|
|
2206
|
-
|
|
2257
|
+
chalk19.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
|
|
2207
2258
|
);
|
|
2208
2259
|
return;
|
|
2209
2260
|
}
|
|
2210
2261
|
const orgId = opts.org || getDefaultOrg();
|
|
2211
2262
|
if (!orgId) {
|
|
2212
|
-
console.error(
|
|
2263
|
+
console.error(chalk19.red("No org specified. Use --org, or --app for a single app."));
|
|
2213
2264
|
process.exit(1);
|
|
2214
2265
|
}
|
|
2215
2266
|
const report = await client4.orgs.usage(orgId, opts.period);
|
|
2216
2267
|
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
2217
2268
|
const inv = report.invoice;
|
|
2218
2269
|
console.log(
|
|
2219
|
-
|
|
2270
|
+
chalk19.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
|
|
2220
2271
|
);
|
|
2221
2272
|
console.log();
|
|
2222
2273
|
printMetrics(report.metrics);
|
|
2223
2274
|
if (report.apps.length > 0) {
|
|
2224
2275
|
console.log();
|
|
2225
|
-
console.log(
|
|
2276
|
+
console.log(chalk19.bold("By app"));
|
|
2226
2277
|
for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
|
|
2227
2278
|
const name = app.slug || app.name || app.appId || "(deleted apps)";
|
|
2228
|
-
console.log(` ${
|
|
2279
|
+
console.log(` ${chalk19.dim(String(name).padEnd(24))} ${money(app.cents)}`);
|
|
2229
2280
|
}
|
|
2230
2281
|
}
|
|
2231
2282
|
console.log();
|
|
2232
2283
|
for (const line of inv.lines) {
|
|
2233
2284
|
console.log(
|
|
2234
|
-
` ${
|
|
2285
|
+
` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk19.dim(
|
|
2235
2286
|
`(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
|
|
2236
2287
|
)}`
|
|
2237
2288
|
);
|
|
2238
2289
|
}
|
|
2239
|
-
console.log(` ${
|
|
2290
|
+
console.log(` ${chalk19.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
|
|
2240
2291
|
if (inv.includedUsageCents > 0) {
|
|
2241
2292
|
console.log(
|
|
2242
|
-
` ${
|
|
2293
|
+
` ${chalk19.dim("Included".padEnd(22))} ${chalk19.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
|
|
2243
2294
|
);
|
|
2244
2295
|
}
|
|
2245
2296
|
if (inv.baseCents > 0) {
|
|
2246
|
-
console.log(` ${
|
|
2297
|
+
console.log(` ${chalk19.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
|
|
2247
2298
|
}
|
|
2248
|
-
console.log(` ${
|
|
2299
|
+
console.log(` ${chalk19.bold("Total".padEnd(22))} ${chalk19.bold(report.total)}`);
|
|
2249
2300
|
console.log();
|
|
2250
|
-
console.log(
|
|
2301
|
+
console.log(chalk19.dim("Usage to date. The period is priced as it stands right now."));
|
|
2251
2302
|
});
|
|
2252
2303
|
usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
|
|
2253
2304
|
const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
|
|
@@ -2255,11 +2306,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2255
2306
|
if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
|
|
2256
2307
|
for (const plan of plans) {
|
|
2257
2308
|
console.log(
|
|
2258
|
-
`${
|
|
2309
|
+
`${chalk19.bold(plan.name)} ${chalk19.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk19.dim(`, ${plan.includedUsage} usage included`) : "")
|
|
2259
2310
|
);
|
|
2260
2311
|
const l = plan.limits;
|
|
2261
2312
|
console.log(
|
|
2262
|
-
|
|
2313
|
+
chalk19.dim(
|
|
2263
2314
|
` ${l.apps} apps \xB7 ${l.instancesPerApp} instances/app \xB7 up to ${l.maxCpu} CPU / ${l.maxMemory} MB \xB7 ${l.maxStorageMb} MB storage${l.postgres ? " \xB7 postgres" : ""}${l.customDomains ? " \xB7 custom domains" : ""}`
|
|
2264
2315
|
)
|
|
2265
2316
|
);
|
|
@@ -2268,11 +2319,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2268
2319
|
});
|
|
2269
2320
|
function printMetrics(metrics) {
|
|
2270
2321
|
if (metrics.length === 0) {
|
|
2271
|
-
console.log(
|
|
2322
|
+
console.log(chalk19.dim(" Nothing metered in this period yet."));
|
|
2272
2323
|
return;
|
|
2273
2324
|
}
|
|
2274
2325
|
for (const m of metrics) {
|
|
2275
|
-
console.log(` ${
|
|
2326
|
+
console.log(` ${chalk19.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
|
|
2276
2327
|
}
|
|
2277
2328
|
}
|
|
2278
2329
|
function round(n) {
|
|
@@ -2287,8 +2338,9 @@ function period(start, end) {
|
|
|
2287
2338
|
}
|
|
2288
2339
|
|
|
2289
2340
|
// src/index.ts
|
|
2341
|
+
installPromptCrashGuard();
|
|
2290
2342
|
var program = new Command18();
|
|
2291
|
-
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.
|
|
2343
|
+
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.1");
|
|
2292
2344
|
program.addCommand(loginCommand);
|
|
2293
2345
|
program.addCommand(logoutCommand);
|
|
2294
2346
|
program.addCommand(initCommand);
|