@videan/cli 0.3.1 → 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/README.md +5 -0
- package/dist/index.js +247 -144
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ import os from "node:os";
|
|
|
14
14
|
var DEFAULT_API_URL = "https://api.videan.xyz/v1";
|
|
15
15
|
var APP_DOMAIN = process.env.CLOUD_APP_DOMAIN || "videan.xyz";
|
|
16
16
|
var MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
17
|
+
var APP_PORT = 3e3;
|
|
18
|
+
var LAMBDA_ADAPTER = `COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
|
|
19
|
+
ENV AWS_LWA_PORT=${APP_PORT}`;
|
|
17
20
|
var FARGATE_SIZES = {
|
|
18
21
|
256: { min: 512, max: 2048, step: 512 },
|
|
19
22
|
512: { min: 1024, max: 4096, step: 1024 },
|
|
@@ -170,7 +173,9 @@ var AppsNamespace = class {
|
|
|
170
173
|
storage_enabled: opts.storage,
|
|
171
174
|
email_enabled: opts.email,
|
|
172
175
|
auth_required: opts.authRequired,
|
|
173
|
-
visibility: opts.visibility
|
|
176
|
+
visibility: opts.visibility,
|
|
177
|
+
stack: opts.stack,
|
|
178
|
+
mode: opts.mode
|
|
174
179
|
};
|
|
175
180
|
const res = await this.client.request("POST", "/apps", body);
|
|
176
181
|
return res.data;
|
|
@@ -199,6 +204,8 @@ var AppsNamespace = class {
|
|
|
199
204
|
if (changes.postgres !== void 0) body.postgres_enabled = changes.postgres;
|
|
200
205
|
if (changes.storage !== void 0) body.storage_enabled = changes.storage;
|
|
201
206
|
if (changes.email !== void 0) body.email_enabled = changes.email;
|
|
207
|
+
if (changes.stack !== void 0) body.stack = changes.stack;
|
|
208
|
+
if (changes.mode !== void 0) body.mode = changes.mode;
|
|
202
209
|
const res = await this.client.request(
|
|
203
210
|
"PATCH",
|
|
204
211
|
`/apps/${appId}`,
|
|
@@ -677,6 +684,12 @@ var OrgsNamespace = class {
|
|
|
677
684
|
}
|
|
678
685
|
};
|
|
679
686
|
|
|
687
|
+
// ../shared/src/types.ts
|
|
688
|
+
var APP_MODES = ["container", "serverless"];
|
|
689
|
+
function isAppMode(value) {
|
|
690
|
+
return APP_MODES.includes(value);
|
|
691
|
+
}
|
|
692
|
+
|
|
680
693
|
// ../shared/src/constants.ts
|
|
681
694
|
var SUPPORTED_RUNTIMES = [
|
|
682
695
|
"node-20",
|
|
@@ -694,6 +707,11 @@ function appUrl(slug) {
|
|
|
694
707
|
var CONFIG_FILE_NAME = "cloud.yaml";
|
|
695
708
|
var MAX_FILE_SIZE_BYTES2 = 50 * 1024 * 1024;
|
|
696
709
|
|
|
710
|
+
// ../shared/src/detect.ts
|
|
711
|
+
var APP_PORT2 = 3e3;
|
|
712
|
+
var LAMBDA_ADAPTER2 = `COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
|
|
713
|
+
ENV AWS_LWA_PORT=${APP_PORT2}`;
|
|
714
|
+
|
|
697
715
|
// ../shared/src/sizing.ts
|
|
698
716
|
var FARGATE_SIZES2 = {
|
|
699
717
|
256: { min: 512, max: 2048, step: 512 },
|
|
@@ -715,7 +733,11 @@ var PLANS2 = {
|
|
|
715
733
|
includedUsageCents: 300,
|
|
716
734
|
maxApps: 3,
|
|
717
735
|
maxInstancesPerApp: 1,
|
|
718
|
-
//
|
|
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.
|
|
719
741
|
maxCpu: 512,
|
|
720
742
|
maxMemory: 1024,
|
|
721
743
|
maxStorageMb: 1024,
|
|
@@ -965,6 +987,15 @@ This platform puts a sign-in gate in front of apps unless they are public.
|
|
|
965
987
|
|
|
966
988
|
Secrets: \`videan secrets set KEY=value\` (UPPERCASE keys) \u2014 injected as env
|
|
967
989
|
vars on the next deploy. Never commit credentials to this repo instead.
|
|
990
|
+
|
|
991
|
+
## Execution modes
|
|
992
|
+
|
|
993
|
+
Apps run as a **container** (default: always on, no cold starts) or
|
|
994
|
+
**serverless** (scales to zero, cold start after idle \u2014 good for internal
|
|
995
|
+
tools with sporadic traffic). Set at create (\`videan apps create --mode
|
|
996
|
+
serverless\`) or later (\`videan apps set --mode \u2026\`, applied by the next
|
|
997
|
+
deploy). Serverless refuses cron, background jobs, WebSockets and custom
|
|
998
|
+
Dockerfiles; container mode supports all of them.
|
|
968
999
|
`;
|
|
969
1000
|
var POINTER = "\n## Videan\n\nThis project deploys to Videan \u2014 see VIDEAN.md for the platform contract (port 3000, auth headers, deploy commands) before writing server or login code.\n";
|
|
970
1001
|
async function writeVideanMd(dir) {
|
|
@@ -1436,39 +1467,79 @@ var rollbackCommand = new Command8("rollback").description("Rollback to the prev
|
|
|
1436
1467
|
|
|
1437
1468
|
// src/commands/secrets.ts
|
|
1438
1469
|
import { Command as Command9 } from "commander";
|
|
1439
|
-
import
|
|
1470
|
+
import chalk11 from "chalk";
|
|
1471
|
+
|
|
1472
|
+
// src/prompt.ts
|
|
1440
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
|
|
1441
1511
|
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1442
1512
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1443
1513
|
const apiKey = getApiKey();
|
|
1444
1514
|
requireApiKey(apiKey);
|
|
1445
1515
|
const appId = opts.app || getCurrentApp();
|
|
1446
1516
|
if (!appId) {
|
|
1447
|
-
console.error(
|
|
1517
|
+
console.error(chalk11.red("No app specified."));
|
|
1448
1518
|
process.exit(1);
|
|
1449
1519
|
}
|
|
1450
1520
|
if (!value) {
|
|
1451
|
-
const answers = await
|
|
1452
|
-
{ type: "password", name: "value", message: `Value for ${key}:` }
|
|
1453
|
-
|
|
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
|
+
);
|
|
1454
1525
|
value = answers.value;
|
|
1455
1526
|
}
|
|
1456
1527
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1457
1528
|
await client4.secrets.set(appId, key, value);
|
|
1458
|
-
console.log(
|
|
1529
|
+
console.log(chalk11.green(`Secret "${key}" set successfully.`));
|
|
1459
1530
|
});
|
|
1460
1531
|
secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1461
1532
|
const apiKey = getApiKey();
|
|
1462
1533
|
requireApiKey(apiKey);
|
|
1463
1534
|
const appId = opts.app || getCurrentApp();
|
|
1464
1535
|
if (!appId) {
|
|
1465
|
-
console.error(
|
|
1536
|
+
console.error(chalk11.red("No app specified."));
|
|
1466
1537
|
process.exit(1);
|
|
1467
1538
|
}
|
|
1468
1539
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1469
1540
|
const secrets = await client4.secrets.list(appId);
|
|
1470
1541
|
if (secrets.length === 0) {
|
|
1471
|
-
console.log(
|
|
1542
|
+
console.log(chalk11.gray("No secrets configured."));
|
|
1472
1543
|
return;
|
|
1473
1544
|
}
|
|
1474
1545
|
console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
|
|
@@ -1481,28 +1552,36 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
|
|
|
1481
1552
|
requireApiKey(apiKey);
|
|
1482
1553
|
const appId = opts.app || getCurrentApp();
|
|
1483
1554
|
if (!appId) {
|
|
1484
|
-
console.error(
|
|
1555
|
+
console.error(chalk11.red("No app specified."));
|
|
1485
1556
|
process.exit(1);
|
|
1486
1557
|
}
|
|
1487
1558
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1488
1559
|
await client4.secrets.delete(appId, key);
|
|
1489
|
-
console.log(
|
|
1560
|
+
console.log(chalk11.green(`Secret "${key}" removed.`));
|
|
1490
1561
|
});
|
|
1491
1562
|
|
|
1492
1563
|
// src/commands/apps.ts
|
|
1493
1564
|
import { Command as Command10 } from "commander";
|
|
1494
|
-
import
|
|
1495
|
-
import inquirer4 from "inquirer";
|
|
1565
|
+
import chalk12 from "chalk";
|
|
1496
1566
|
var appsCommand = new Command10("apps").description("Manage apps");
|
|
1497
1567
|
function accessOption(value) {
|
|
1498
1568
|
if (value === void 0) return void 0;
|
|
1499
1569
|
const tier = value.trim().toLowerCase();
|
|
1500
1570
|
if (!isVisibility(tier)) {
|
|
1501
|
-
console.error(
|
|
1571
|
+
console.error(chalk12.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
|
|
1502
1572
|
process.exit(1);
|
|
1503
1573
|
}
|
|
1504
1574
|
return tier;
|
|
1505
1575
|
}
|
|
1576
|
+
function modeOption(value) {
|
|
1577
|
+
if (value === void 0) return void 0;
|
|
1578
|
+
const mode = value.trim().toLowerCase();
|
|
1579
|
+
if (!isAppMode(mode)) {
|
|
1580
|
+
console.error(chalk12.red(`Unknown --mode value "${value}". Use one of: ${APP_MODES.join(", ")}`));
|
|
1581
|
+
process.exit(1);
|
|
1582
|
+
}
|
|
1583
|
+
return mode;
|
|
1584
|
+
}
|
|
1506
1585
|
function describeAccess(app) {
|
|
1507
1586
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1508
1587
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
@@ -1514,7 +1593,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1514
1593
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1515
1594
|
const apps = await client4.apps.list(orgId);
|
|
1516
1595
|
if (apps.length === 0) {
|
|
1517
|
-
console.log(
|
|
1596
|
+
console.log(chalk12.gray("No apps yet. Create one with `videan apps create`."));
|
|
1518
1597
|
return;
|
|
1519
1598
|
}
|
|
1520
1599
|
const widths = [20, 24, 10, 44];
|
|
@@ -1523,23 +1602,31 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1523
1602
|
console.log(formatRow([app.name, app.id, app.status, appUrl(app.slug)], widths));
|
|
1524
1603
|
}
|
|
1525
1604
|
});
|
|
1526
|
-
appsCommand.command("create").description("Create a new app").option("-n, --name <name>", "App name").option("-o, --org <id>", "Organization ID").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("--public", "Serve the app without the platform login gate (same as --access public)").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")} (default: ${DEFAULT_VISIBILITY})`).
|
|
1605
|
+
appsCommand.command("create").description("Create a new app").option("-n, --name <name>", "App name").option("-o, --org <id>", "Organization ID").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("--public", "Serve the app without the platform login gate (same as --access public)").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")} (default: ${DEFAULT_VISIBILITY})`).option(
|
|
1606
|
+
"--stack <label>",
|
|
1607
|
+
"Group this app with its siblings (set the same label on a split frontend and backend)"
|
|
1608
|
+
).option(
|
|
1609
|
+
"--mode <mode>",
|
|
1610
|
+
`How the app runs: ${APP_MODES.join(" | ")} (default: container). serverless scales to zero but runs no cron, jobs or WebSockets`
|
|
1611
|
+
).action(
|
|
1527
1612
|
async (opts) => {
|
|
1528
1613
|
const apiKey = getApiKey();
|
|
1529
1614
|
requireApiKey(apiKey);
|
|
1530
1615
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1531
1616
|
let name = opts.name;
|
|
1532
1617
|
if (!name) {
|
|
1533
|
-
const answers = await
|
|
1534
|
-
{ type: "input", name: "name", message: "App name:" }
|
|
1535
|
-
|
|
1618
|
+
const answers = await prompt(
|
|
1619
|
+
[{ type: "input", name: "name", message: "App name:" }],
|
|
1620
|
+
"Pass the name: videan apps create --name <name>"
|
|
1621
|
+
);
|
|
1536
1622
|
name = answers.name;
|
|
1537
1623
|
}
|
|
1538
1624
|
if (!name) {
|
|
1539
|
-
console.error(
|
|
1625
|
+
console.error(chalk12.red("An app name is required."));
|
|
1540
1626
|
process.exit(1);
|
|
1541
1627
|
}
|
|
1542
1628
|
const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
|
|
1629
|
+
const mode = modeOption(opts.mode);
|
|
1543
1630
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1544
1631
|
const app = await client4.apps.create({
|
|
1545
1632
|
name,
|
|
@@ -1547,35 +1634,40 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1547
1634
|
postgres: opts.postgres ?? false,
|
|
1548
1635
|
storage: opts.storage ?? false,
|
|
1549
1636
|
email: opts.email ?? false,
|
|
1550
|
-
visibility
|
|
1637
|
+
visibility,
|
|
1638
|
+
stack: opts.stack,
|
|
1639
|
+
mode
|
|
1551
1640
|
});
|
|
1552
1641
|
setCurrentApp(app.id);
|
|
1553
|
-
console.log(
|
|
1554
|
-
console.log(
|
|
1555
|
-
console.log(
|
|
1556
|
-
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."));
|
|
1557
1647
|
}
|
|
1558
1648
|
);
|
|
1559
|
-
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("--json", "Print the raw response").action(
|
|
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(
|
|
1560
1650
|
async (opts) => {
|
|
1561
1651
|
const apiKey = getApiKey();
|
|
1562
1652
|
requireApiKey(apiKey);
|
|
1563
1653
|
const appId = opts.app || getCurrentApp();
|
|
1564
1654
|
if (!appId) {
|
|
1565
|
-
console.error(
|
|
1655
|
+
console.error(chalk12.red("No app specified. Use --app or link an app."));
|
|
1566
1656
|
process.exit(1);
|
|
1567
1657
|
}
|
|
1568
1658
|
if (opts.public && opts.private) {
|
|
1569
|
-
console.error(
|
|
1659
|
+
console.error(chalk12.red("--public and --private contradict each other."));
|
|
1570
1660
|
process.exit(1);
|
|
1571
1661
|
}
|
|
1572
1662
|
const access = accessOption(opts.access);
|
|
1573
1663
|
if (access && (opts.public || opts.private)) {
|
|
1574
|
-
console.error(
|
|
1664
|
+
console.error(chalk12.red("--access already says who may open the app; drop --public/--private."));
|
|
1575
1665
|
process.exit(1);
|
|
1576
1666
|
}
|
|
1667
|
+
const mode = modeOption(opts.mode);
|
|
1577
1668
|
const changes = {};
|
|
1578
1669
|
if (opts.name) changes.name = opts.name;
|
|
1670
|
+
if (mode) changes.mode = mode;
|
|
1579
1671
|
if (opts.cpu !== void 0) changes.cpu = intOption("--cpu", opts.cpu);
|
|
1580
1672
|
if (opts.memory !== void 0) changes.memory = intOption("--memory", opts.memory);
|
|
1581
1673
|
if (opts.instances !== void 0) {
|
|
@@ -1591,8 +1683,10 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1591
1683
|
if (opts.postgres) changes.postgres = true;
|
|
1592
1684
|
if (opts.storage) changes.storage = true;
|
|
1593
1685
|
if (opts.email) changes.email = true;
|
|
1686
|
+
if (opts.stack === false) changes.stack = null;
|
|
1687
|
+
else if (typeof opts.stack === "string") changes.stack = opts.stack;
|
|
1594
1688
|
if (Object.keys(changes).length === 0) {
|
|
1595
|
-
console.error(
|
|
1689
|
+
console.error(chalk12.red("Nothing to change. Pass at least one option."));
|
|
1596
1690
|
process.exit(1);
|
|
1597
1691
|
}
|
|
1598
1692
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1601,7 +1695,8 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1601
1695
|
console.log(JSON.stringify(app, null, 2));
|
|
1602
1696
|
return;
|
|
1603
1697
|
}
|
|
1604
|
-
console.log(
|
|
1698
|
+
console.log(chalk12.green(`Updated ${app.name} (${app.id})`));
|
|
1699
|
+
console.log(` Mode: ${app.mode ?? "container"}`);
|
|
1605
1700
|
console.log(` Access: ${describeAccess(app)}`);
|
|
1606
1701
|
console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
|
|
1607
1702
|
console.log(
|
|
@@ -1609,7 +1704,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1609
1704
|
);
|
|
1610
1705
|
if (app.restartRequired) {
|
|
1611
1706
|
console.log(
|
|
1612
|
-
|
|
1707
|
+
chalk12.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
|
|
1613
1708
|
);
|
|
1614
1709
|
}
|
|
1615
1710
|
}
|
|
@@ -1617,37 +1712,42 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1617
1712
|
function intOption(flag, raw) {
|
|
1618
1713
|
const n = Number(raw);
|
|
1619
1714
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1620
|
-
console.error(
|
|
1715
|
+
console.error(chalk12.red(`${flag} expects a positive whole number, got "${raw}".`));
|
|
1621
1716
|
process.exit(1);
|
|
1622
1717
|
}
|
|
1623
1718
|
return n;
|
|
1624
1719
|
}
|
|
1625
|
-
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) => {
|
|
1626
1721
|
const apiKey = getApiKey();
|
|
1627
1722
|
requireApiKey(apiKey);
|
|
1628
|
-
|
|
1629
|
-
{
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
+
}
|
|
1637
1737
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1638
1738
|
const { warnings } = await client4.apps.delete(id);
|
|
1639
|
-
console.log(
|
|
1739
|
+
console.log(chalk12.green("App deleted."));
|
|
1640
1740
|
if (warnings.length > 0) {
|
|
1641
|
-
console.log(
|
|
1741
|
+
console.log(chalk12.yellow("\nSome resources could not be released:"));
|
|
1642
1742
|
for (const w of warnings) {
|
|
1643
|
-
console.log(
|
|
1743
|
+
console.log(chalk12.yellow(` ${w.step}: ${w.error || "unknown error"}`));
|
|
1644
1744
|
}
|
|
1645
1745
|
}
|
|
1646
1746
|
});
|
|
1647
1747
|
|
|
1648
1748
|
// src/commands/status.ts
|
|
1649
1749
|
import { Command as Command11 } from "commander";
|
|
1650
|
-
import
|
|
1750
|
+
import chalk13 from "chalk";
|
|
1651
1751
|
function accessLine(app) {
|
|
1652
1752
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1653
1753
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
@@ -1657,42 +1757,41 @@ var statusCommand = new Command11("status").description("Show current app status
|
|
|
1657
1757
|
requireApiKey(apiKey);
|
|
1658
1758
|
const appId = opts.app || getCurrentApp();
|
|
1659
1759
|
if (!appId) {
|
|
1660
|
-
console.error(
|
|
1760
|
+
console.error(chalk13.red("No app specified. Use --app or link an app."));
|
|
1661
1761
|
process.exit(1);
|
|
1662
1762
|
}
|
|
1663
1763
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1664
1764
|
const app = await client4.apps.get(appId);
|
|
1665
1765
|
const health = await client4.apps.health(appId);
|
|
1666
|
-
console.log(
|
|
1766
|
+
console.log(chalk13.bold(app.name));
|
|
1667
1767
|
console.log(` ID: ${app.id}`);
|
|
1668
1768
|
console.log(` Created: ${app.createdAt}`);
|
|
1669
1769
|
console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
|
|
1670
1770
|
console.log(` Access: ${accessLine(app)}`);
|
|
1671
1771
|
console.log(` State: ${formatStatus(health.state)}`);
|
|
1672
|
-
console.log(` Healthy: ${health.healthy ?
|
|
1673
|
-
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))}`);
|
|
1674
1774
|
const task = health.task;
|
|
1675
1775
|
if (task) {
|
|
1676
1776
|
if (task.exit_code !== void 0 && task.exit_code !== null) {
|
|
1677
1777
|
const code = String(task.exit_code);
|
|
1678
|
-
console.log(` Exit code: ${task.exit_code === 0 ? code :
|
|
1778
|
+
console.log(` Exit code: ${task.exit_code === 0 ? code : chalk13.red(code)}`);
|
|
1679
1779
|
}
|
|
1680
1780
|
if (task.stopped_reason) {
|
|
1681
|
-
console.log(` Stopped: ${
|
|
1781
|
+
console.log(` Stopped: ${chalk13.yellow(task.stopped_reason)}`);
|
|
1682
1782
|
}
|
|
1683
1783
|
if (task.container_reason) {
|
|
1684
|
-
console.log(` Detail: ${
|
|
1784
|
+
console.log(` Detail: ${chalk13.yellow(task.container_reason)}`);
|
|
1685
1785
|
}
|
|
1686
1786
|
}
|
|
1687
1787
|
if (!health.healthy) {
|
|
1688
|
-
console.log(
|
|
1788
|
+
console.log(chalk13.gray("\nRun `videan logs` to see why."));
|
|
1689
1789
|
}
|
|
1690
1790
|
});
|
|
1691
1791
|
|
|
1692
1792
|
// src/commands/keys.ts
|
|
1693
1793
|
import { Command as Command12 } from "commander";
|
|
1694
|
-
import
|
|
1695
|
-
import inquirer5 from "inquirer";
|
|
1794
|
+
import chalk14 from "chalk";
|
|
1696
1795
|
var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
|
|
1697
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(
|
|
1698
1797
|
"--app <id>",
|
|
@@ -1705,7 +1804,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1705
1804
|
if (opts.expires !== void 0) {
|
|
1706
1805
|
expiresInDays = Number(opts.expires);
|
|
1707
1806
|
if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
|
|
1708
|
-
console.error(
|
|
1807
|
+
console.error(chalk14.red("--expires must be a whole number of days, at least 1."));
|
|
1709
1808
|
process.exit(1);
|
|
1710
1809
|
}
|
|
1711
1810
|
}
|
|
@@ -1716,22 +1815,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1716
1815
|
expiresInDays,
|
|
1717
1816
|
appId: opts.app
|
|
1718
1817
|
});
|
|
1719
|
-
console.log(
|
|
1818
|
+
console.log(chalk14.green(`Created key "${key.name}" (${key.id})`));
|
|
1720
1819
|
console.log(`
|
|
1721
|
-
${
|
|
1820
|
+
${chalk14.bold(key.key)}
|
|
1722
1821
|
`);
|
|
1723
|
-
console.log(
|
|
1822
|
+
console.log(chalk14.yellow("Copy it now. It cannot be shown again."));
|
|
1724
1823
|
if (key.appId) {
|
|
1725
1824
|
console.log(
|
|
1726
|
-
|
|
1825
|
+
chalk14.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
|
|
1727
1826
|
);
|
|
1728
1827
|
} else {
|
|
1729
1828
|
console.log(
|
|
1730
|
-
|
|
1829
|
+
chalk14.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
|
|
1731
1830
|
);
|
|
1732
1831
|
}
|
|
1733
1832
|
if (key.expiresAt) {
|
|
1734
|
-
console.log(
|
|
1833
|
+
console.log(chalk14.gray(`Expires ${key.expiresAt}`));
|
|
1735
1834
|
}
|
|
1736
1835
|
});
|
|
1737
1836
|
keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
@@ -1741,13 +1840,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1741
1840
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1742
1841
|
const keys = await client4.apiKeys.list(orgId);
|
|
1743
1842
|
if (keys.length === 0) {
|
|
1744
|
-
console.log(
|
|
1843
|
+
console.log(chalk14.gray("No API keys. Create one with `videan keys create <name>`."));
|
|
1745
1844
|
return;
|
|
1746
1845
|
}
|
|
1747
1846
|
const widths = [24, 24, 16, 22, 12, 24];
|
|
1748
1847
|
console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
|
|
1749
1848
|
for (const key of keys) {
|
|
1750
|
-
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");
|
|
1751
1850
|
console.log(formatRow(
|
|
1752
1851
|
[
|
|
1753
1852
|
key.name,
|
|
@@ -1761,21 +1860,24 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1761
1860
|
));
|
|
1762
1861
|
}
|
|
1763
1862
|
});
|
|
1764
|
-
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) => {
|
|
1765
1864
|
const apiKey = getApiKey();
|
|
1766
1865
|
requireApiKey(apiKey);
|
|
1767
|
-
|
|
1768
|
-
{
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
+
}
|
|
1771
1873
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1772
1874
|
await client4.apiKeys.revoke(id);
|
|
1773
|
-
console.log(
|
|
1875
|
+
console.log(chalk14.green("Key revoked."));
|
|
1774
1876
|
});
|
|
1775
1877
|
|
|
1776
1878
|
// src/commands/orgs.ts
|
|
1777
1879
|
import { Command as Command13 } from "commander";
|
|
1778
|
-
import
|
|
1880
|
+
import chalk15 from "chalk";
|
|
1779
1881
|
var orgsCommand = new Command13("orgs").description("Manage organizations");
|
|
1780
1882
|
orgsCommand.command("list").description("List your organizations").action(async () => {
|
|
1781
1883
|
const apiKey = getApiKey();
|
|
@@ -1784,14 +1886,14 @@ orgsCommand.command("list").description("List your organizations").action(async
|
|
|
1784
1886
|
const orgs = await client4.orgs.list();
|
|
1785
1887
|
const current = getDefaultOrg();
|
|
1786
1888
|
if (orgs.length === 0) {
|
|
1787
|
-
console.log(
|
|
1889
|
+
console.log(chalk15.gray("No organizations. Create one with `videan orgs create <name>`."));
|
|
1788
1890
|
return;
|
|
1789
1891
|
}
|
|
1790
1892
|
const widths = [24, 24, 10, 8];
|
|
1791
1893
|
console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
|
|
1792
1894
|
for (const org of orgs) {
|
|
1793
1895
|
console.log(formatRow(
|
|
1794
|
-
[org.name, org.id, org.plan, org.id === current ?
|
|
1896
|
+
[org.name, org.id, org.plan, org.id === current ? chalk15.green("default") : ""],
|
|
1795
1897
|
widths
|
|
1796
1898
|
));
|
|
1797
1899
|
}
|
|
@@ -1802,7 +1904,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
|
|
|
1802
1904
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1803
1905
|
const org = await client4.orgs.create(name);
|
|
1804
1906
|
if (!getDefaultOrg()) setDefaultOrg(org.id);
|
|
1805
|
-
console.log(
|
|
1907
|
+
console.log(chalk15.green(`Organization "${org.name}" created (${org.id})`));
|
|
1806
1908
|
});
|
|
1807
1909
|
orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
|
|
1808
1910
|
const apiKey = getApiKey();
|
|
@@ -1810,7 +1912,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
|
|
|
1810
1912
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1811
1913
|
const org = await client4.orgs.get(id);
|
|
1812
1914
|
setDefaultOrg(org.id);
|
|
1813
|
-
console.log(
|
|
1915
|
+
console.log(chalk15.green(`Default organization set to "${org.name}".`));
|
|
1814
1916
|
});
|
|
1815
1917
|
orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
1816
1918
|
const apiKey = getApiKey();
|
|
@@ -1833,12 +1935,12 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1833
1935
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1834
1936
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1835
1937
|
await client4.orgs.invite(orgId, email);
|
|
1836
|
-
console.log(
|
|
1938
|
+
console.log(chalk15.green(`Invited ${email}.`));
|
|
1837
1939
|
});
|
|
1838
1940
|
|
|
1839
1941
|
// src/commands/storage.ts
|
|
1840
1942
|
import { Command as Command14 } from "commander";
|
|
1841
|
-
import
|
|
1943
|
+
import chalk16 from "chalk";
|
|
1842
1944
|
import fs5 from "fs-extra";
|
|
1843
1945
|
import path5 from "node:path";
|
|
1844
1946
|
var storageCommand = new Command14("storage").description("Store files for an app");
|
|
@@ -1850,7 +1952,7 @@ function client() {
|
|
|
1850
1952
|
function requireApp(opt) {
|
|
1851
1953
|
const appId = opt || getCurrentApp();
|
|
1852
1954
|
if (!appId) {
|
|
1853
|
-
console.error(
|
|
1955
|
+
console.error(chalk16.red("No app specified. Use --app or link an app."));
|
|
1854
1956
|
process.exit(1);
|
|
1855
1957
|
}
|
|
1856
1958
|
return appId;
|
|
@@ -1873,7 +1975,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1873
1975
|
return;
|
|
1874
1976
|
}
|
|
1875
1977
|
if (objects.length === 0) {
|
|
1876
|
-
console.log(
|
|
1978
|
+
console.log(chalk16.gray("No objects stored. Upload one with `videan storage put <file>`."));
|
|
1877
1979
|
return;
|
|
1878
1980
|
}
|
|
1879
1981
|
const widths = [52, 12, 24];
|
|
@@ -1882,13 +1984,13 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1882
1984
|
console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
|
|
1883
1985
|
}
|
|
1884
1986
|
const total = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1885
|
-
console.log(
|
|
1987
|
+
console.log(chalk16.dim(`
|
|
1886
1988
|
${objects.length} object(s), ${human(total)}`));
|
|
1887
1989
|
});
|
|
1888
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) => {
|
|
1889
1991
|
const appId = requireApp(opts.app);
|
|
1890
1992
|
if (!await fs5.pathExists(file)) {
|
|
1891
|
-
console.error(
|
|
1993
|
+
console.error(chalk16.red(`No such file: ${file}`));
|
|
1892
1994
|
process.exit(1);
|
|
1893
1995
|
}
|
|
1894
1996
|
const body = await fs5.readFile(file);
|
|
@@ -1897,27 +1999,27 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
|
|
|
1897
1999
|
contentType: opts.type
|
|
1898
2000
|
});
|
|
1899
2001
|
console.log(
|
|
1900
|
-
|
|
2002
|
+
chalk16.green(`Uploaded ${objectKey}`) + chalk16.dim(` (${human(body.length)})`)
|
|
1901
2003
|
);
|
|
1902
|
-
if (stored.etag) console.log(
|
|
2004
|
+
if (stored.etag) console.log(chalk16.dim(`etag ${stored.etag}`));
|
|
1903
2005
|
});
|
|
1904
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) => {
|
|
1905
2007
|
const appId = requireApp(opts.app);
|
|
1906
2008
|
const bytes = await client().storage.download(appId, key);
|
|
1907
2009
|
const dest = file || path5.basename(key);
|
|
1908
2010
|
await fs5.writeFile(dest, bytes);
|
|
1909
|
-
console.log(
|
|
2011
|
+
console.log(chalk16.green(`Wrote ${dest}`) + chalk16.dim(` (${human(bytes.length)})`));
|
|
1910
2012
|
});
|
|
1911
2013
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
1912
2014
|
const appId = requireApp(opts.app);
|
|
1913
2015
|
await client().storage.delete(appId, key);
|
|
1914
|
-
console.log(
|
|
2016
|
+
console.log(chalk16.green(`Deleted ${key}`));
|
|
1915
2017
|
});
|
|
1916
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) => {
|
|
1917
2019
|
const appId = requireApp(opts.app);
|
|
1918
2020
|
const expiresIn = Number(opts.expires);
|
|
1919
2021
|
if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
|
|
1920
|
-
console.error(
|
|
2022
|
+
console.error(chalk16.red("--expires must be a whole number of seconds between 30 and 3600."));
|
|
1921
2023
|
process.exit(1);
|
|
1922
2024
|
}
|
|
1923
2025
|
const signed = await client().storage.presign(appId, key, {
|
|
@@ -1927,7 +2029,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
|
|
|
1927
2029
|
});
|
|
1928
2030
|
console.log(signed.url);
|
|
1929
2031
|
console.error(
|
|
1930
|
-
|
|
2032
|
+
chalk16.yellow(
|
|
1931
2033
|
`This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
|
|
1932
2034
|
)
|
|
1933
2035
|
);
|
|
@@ -1940,7 +2042,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1940
2042
|
|
|
1941
2043
|
// src/commands/email.ts
|
|
1942
2044
|
import { Command as Command15 } from "commander";
|
|
1943
|
-
import
|
|
2045
|
+
import chalk17 from "chalk";
|
|
1944
2046
|
import fs6 from "fs-extra";
|
|
1945
2047
|
var emailCommand = new Command15("email").description("Send email from an app");
|
|
1946
2048
|
function client2() {
|
|
@@ -1951,7 +2053,7 @@ function client2() {
|
|
|
1951
2053
|
function requireApp2(opt) {
|
|
1952
2054
|
const appId = opt || getCurrentApp();
|
|
1953
2055
|
if (!appId) {
|
|
1954
|
-
console.error(
|
|
2056
|
+
console.error(chalk17.red("No app specified. Use --app or link an app."));
|
|
1955
2057
|
process.exit(1);
|
|
1956
2058
|
}
|
|
1957
2059
|
return appId;
|
|
@@ -1967,7 +2069,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1967
2069
|
else text = contents;
|
|
1968
2070
|
}
|
|
1969
2071
|
if (!text && !html) {
|
|
1970
|
-
console.error(
|
|
2072
|
+
console.error(chalk17.red("Nothing to send. Pass --text, --html or --body-file."));
|
|
1971
2073
|
process.exit(1);
|
|
1972
2074
|
}
|
|
1973
2075
|
const sent = await client2().email.send(appId, {
|
|
@@ -1981,12 +2083,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1981
2083
|
console.log(JSON.stringify(sent, null, 2));
|
|
1982
2084
|
return;
|
|
1983
2085
|
}
|
|
1984
|
-
console.log(
|
|
2086
|
+
console.log(chalk17.green("Sent"));
|
|
1985
2087
|
console.log(` From: ${sent.from}`);
|
|
1986
2088
|
console.log(` To: ${sent.to.join(", ")}`);
|
|
1987
2089
|
console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
|
|
1988
2090
|
console.log(
|
|
1989
|
-
|
|
2091
|
+
chalk17.dim(
|
|
1990
2092
|
` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
|
|
1991
2093
|
)
|
|
1992
2094
|
);
|
|
@@ -2001,12 +2103,12 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
2001
2103
|
console.log(`Sent today: ${quota.sent}`);
|
|
2002
2104
|
console.log(`Daily quota: ${quota.quota}`);
|
|
2003
2105
|
console.log(`Remaining: ${quota.remaining}`);
|
|
2004
|
-
console.log(
|
|
2106
|
+
console.log(chalk17.dim(`Resets at: ${quota.resetsAt}`));
|
|
2005
2107
|
});
|
|
2006
2108
|
|
|
2007
2109
|
// src/commands/jobs.ts
|
|
2008
2110
|
import { Command as Command16 } from "commander";
|
|
2009
|
-
import
|
|
2111
|
+
import chalk18 from "chalk";
|
|
2010
2112
|
import fs7 from "fs-extra";
|
|
2011
2113
|
var jobsCommand = new Command16("jobs").description("Enqueue and inspect background jobs");
|
|
2012
2114
|
function client3() {
|
|
@@ -2017,7 +2119,7 @@ function client3() {
|
|
|
2017
2119
|
function requireApp3(opt) {
|
|
2018
2120
|
const appId = opt || getCurrentApp();
|
|
2019
2121
|
if (!appId) {
|
|
2020
|
-
console.error(
|
|
2122
|
+
console.error(chalk18.red("No app specified. Use --app or run `videan init` first."));
|
|
2021
2123
|
process.exit(1);
|
|
2022
2124
|
}
|
|
2023
2125
|
return appId;
|
|
@@ -2025,20 +2127,20 @@ function requireApp3(opt) {
|
|
|
2025
2127
|
function intOption2(flag, raw) {
|
|
2026
2128
|
const n = Number(raw);
|
|
2027
2129
|
if (!Number.isInteger(n) || n < 0) {
|
|
2028
|
-
console.error(
|
|
2130
|
+
console.error(chalk18.red(`${flag} expects a whole number, got "${raw}".`));
|
|
2029
2131
|
process.exit(1);
|
|
2030
2132
|
}
|
|
2031
2133
|
return n;
|
|
2032
2134
|
}
|
|
2033
2135
|
var STATUS_COLOR = {
|
|
2034
|
-
queued:
|
|
2035
|
-
running:
|
|
2036
|
-
succeeded:
|
|
2037
|
-
dead:
|
|
2038
|
-
cancelled:
|
|
2136
|
+
queued: chalk18.yellow,
|
|
2137
|
+
running: chalk18.cyan,
|
|
2138
|
+
succeeded: chalk18.green,
|
|
2139
|
+
dead: chalk18.red,
|
|
2140
|
+
cancelled: chalk18.gray
|
|
2039
2141
|
};
|
|
2040
2142
|
function statusLabel(status) {
|
|
2041
|
-
return (STATUS_COLOR[status] ??
|
|
2143
|
+
return (STATUS_COLOR[status] ?? chalk18.white)(status);
|
|
2042
2144
|
}
|
|
2043
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) => {
|
|
2044
2146
|
const appId = requireApp3(opts.app);
|
|
@@ -2048,7 +2150,7 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2048
2150
|
try {
|
|
2049
2151
|
payload = JSON.parse(raw);
|
|
2050
2152
|
} catch (e) {
|
|
2051
|
-
console.error(
|
|
2153
|
+
console.error(chalk18.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
|
|
2052
2154
|
process.exit(1);
|
|
2053
2155
|
}
|
|
2054
2156
|
}
|
|
@@ -2066,13 +2168,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2066
2168
|
return;
|
|
2067
2169
|
}
|
|
2068
2170
|
if (job.deduplicated) {
|
|
2069
|
-
console.log(
|
|
2171
|
+
console.log(chalk18.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
|
|
2070
2172
|
} else {
|
|
2071
|
-
console.log(
|
|
2173
|
+
console.log(chalk18.green(`Queued ${job.id}`));
|
|
2072
2174
|
}
|
|
2073
|
-
console.log(
|
|
2175
|
+
console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2074
2176
|
if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
|
|
2075
|
-
console.log(
|
|
2177
|
+
console.log(chalk18.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
|
|
2076
2178
|
}
|
|
2077
2179
|
});
|
|
2078
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) => {
|
|
@@ -2087,17 +2189,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
|
|
|
2087
2189
|
return;
|
|
2088
2190
|
}
|
|
2089
2191
|
const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
|
|
2090
|
-
console.log(depth ||
|
|
2192
|
+
console.log(depth || chalk18.gray("No jobs yet."));
|
|
2091
2193
|
if (jobs.length === 0) return;
|
|
2092
2194
|
console.log();
|
|
2093
2195
|
for (const job of jobs) {
|
|
2094
|
-
const attempts = job.attempts > 0 ?
|
|
2196
|
+
const attempts = job.attempts > 0 ? chalk18.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
|
|
2095
2197
|
console.log(
|
|
2096
|
-
`${statusLabel(job.status).padEnd(20)} ${
|
|
2198
|
+
`${statusLabel(job.status).padEnd(20)} ${chalk18.white(job.id)} ${chalk18.gray(
|
|
2097
2199
|
`${job.queue} ${job.path}`
|
|
2098
2200
|
)}${attempts}`
|
|
2099
2201
|
);
|
|
2100
|
-
if (job.error) console.log(
|
|
2202
|
+
if (job.error) console.log(chalk18.red(` ${job.error}`));
|
|
2101
2203
|
}
|
|
2102
2204
|
});
|
|
2103
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) => {
|
|
@@ -2106,17 +2208,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2106
2208
|
console.log(JSON.stringify(job, null, 2));
|
|
2107
2209
|
return;
|
|
2108
2210
|
}
|
|
2109
|
-
console.log(`${
|
|
2110
|
-
console.log(
|
|
2111
|
-
console.log(
|
|
2112
|
-
if (job.statusCode) console.log(
|
|
2113
|
-
if (job.durationMs) console.log(
|
|
2114
|
-
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}`));
|
|
2115
2217
|
if (job.status === "queued" && job.attempts > 0) {
|
|
2116
|
-
console.log(
|
|
2218
|
+
console.log(chalk18.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
|
|
2117
2219
|
}
|
|
2118
2220
|
if (job.payload !== null && job.payload !== void 0) {
|
|
2119
|
-
console.log(
|
|
2221
|
+
console.log(chalk18.gray(" Payload:"));
|
|
2120
2222
|
console.log(
|
|
2121
2223
|
JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
|
|
2122
2224
|
);
|
|
@@ -2124,16 +2226,16 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2124
2226
|
});
|
|
2125
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) => {
|
|
2126
2228
|
const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
|
|
2127
|
-
console.log(
|
|
2229
|
+
console.log(chalk18.green(`Requeued ${job.id}`));
|
|
2128
2230
|
});
|
|
2129
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) => {
|
|
2130
2232
|
const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
|
|
2131
|
-
console.log(
|
|
2233
|
+
console.log(chalk18.green(`Cancelled ${job.id}`));
|
|
2132
2234
|
});
|
|
2133
2235
|
|
|
2134
2236
|
// src/commands/usage.ts
|
|
2135
2237
|
import { Command as Command17 } from "commander";
|
|
2136
|
-
import
|
|
2238
|
+
import chalk19 from "chalk";
|
|
2137
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) => {
|
|
2138
2240
|
const apiKey = getApiKey();
|
|
2139
2241
|
requireApiKey(apiKey);
|
|
@@ -2142,61 +2244,61 @@ var usageCommand = new Command17("usage").description("Metered usage and cost fo
|
|
|
2142
2244
|
const appId = opts.app || getCurrentApp();
|
|
2143
2245
|
const report2 = await client4.apps.usage(appId, opts.period);
|
|
2144
2246
|
if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
|
|
2145
|
-
console.log(
|
|
2247
|
+
console.log(chalk19.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
|
|
2146
2248
|
console.log();
|
|
2147
2249
|
printMetrics(report2.metrics);
|
|
2148
2250
|
console.log();
|
|
2149
2251
|
for (const line of report2.lines) {
|
|
2150
|
-
console.log(` ${
|
|
2252
|
+
console.log(` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)}`);
|
|
2151
2253
|
}
|
|
2152
|
-
console.log(` ${
|
|
2254
|
+
console.log(` ${chalk19.bold("Share of the bill".padEnd(22))} ${chalk19.bold(report2.cost)}`);
|
|
2153
2255
|
console.log();
|
|
2154
2256
|
console.log(
|
|
2155
|
-
|
|
2257
|
+
chalk19.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
|
|
2156
2258
|
);
|
|
2157
2259
|
return;
|
|
2158
2260
|
}
|
|
2159
2261
|
const orgId = opts.org || getDefaultOrg();
|
|
2160
2262
|
if (!orgId) {
|
|
2161
|
-
console.error(
|
|
2263
|
+
console.error(chalk19.red("No org specified. Use --org, or --app for a single app."));
|
|
2162
2264
|
process.exit(1);
|
|
2163
2265
|
}
|
|
2164
2266
|
const report = await client4.orgs.usage(orgId, opts.period);
|
|
2165
2267
|
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
2166
2268
|
const inv = report.invoice;
|
|
2167
2269
|
console.log(
|
|
2168
|
-
|
|
2270
|
+
chalk19.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
|
|
2169
2271
|
);
|
|
2170
2272
|
console.log();
|
|
2171
2273
|
printMetrics(report.metrics);
|
|
2172
2274
|
if (report.apps.length > 0) {
|
|
2173
2275
|
console.log();
|
|
2174
|
-
console.log(
|
|
2276
|
+
console.log(chalk19.bold("By app"));
|
|
2175
2277
|
for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
|
|
2176
2278
|
const name = app.slug || app.name || app.appId || "(deleted apps)";
|
|
2177
|
-
console.log(` ${
|
|
2279
|
+
console.log(` ${chalk19.dim(String(name).padEnd(24))} ${money(app.cents)}`);
|
|
2178
2280
|
}
|
|
2179
2281
|
}
|
|
2180
2282
|
console.log();
|
|
2181
2283
|
for (const line of inv.lines) {
|
|
2182
2284
|
console.log(
|
|
2183
|
-
` ${
|
|
2285
|
+
` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk19.dim(
|
|
2184
2286
|
`(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
|
|
2185
2287
|
)}`
|
|
2186
2288
|
);
|
|
2187
2289
|
}
|
|
2188
|
-
console.log(` ${
|
|
2290
|
+
console.log(` ${chalk19.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
|
|
2189
2291
|
if (inv.includedUsageCents > 0) {
|
|
2190
2292
|
console.log(
|
|
2191
|
-
` ${
|
|
2293
|
+
` ${chalk19.dim("Included".padEnd(22))} ${chalk19.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
|
|
2192
2294
|
);
|
|
2193
2295
|
}
|
|
2194
2296
|
if (inv.baseCents > 0) {
|
|
2195
|
-
console.log(` ${
|
|
2297
|
+
console.log(` ${chalk19.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
|
|
2196
2298
|
}
|
|
2197
|
-
console.log(` ${
|
|
2299
|
+
console.log(` ${chalk19.bold("Total".padEnd(22))} ${chalk19.bold(report.total)}`);
|
|
2198
2300
|
console.log();
|
|
2199
|
-
console.log(
|
|
2301
|
+
console.log(chalk19.dim("Usage to date. The period is priced as it stands right now."));
|
|
2200
2302
|
});
|
|
2201
2303
|
usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
|
|
2202
2304
|
const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
|
|
@@ -2204,11 +2306,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2204
2306
|
if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
|
|
2205
2307
|
for (const plan of plans) {
|
|
2206
2308
|
console.log(
|
|
2207
|
-
`${
|
|
2309
|
+
`${chalk19.bold(plan.name)} ${chalk19.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk19.dim(`, ${plan.includedUsage} usage included`) : "")
|
|
2208
2310
|
);
|
|
2209
2311
|
const l = plan.limits;
|
|
2210
2312
|
console.log(
|
|
2211
|
-
|
|
2313
|
+
chalk19.dim(
|
|
2212
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" : ""}`
|
|
2213
2315
|
)
|
|
2214
2316
|
);
|
|
@@ -2217,11 +2319,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2217
2319
|
});
|
|
2218
2320
|
function printMetrics(metrics) {
|
|
2219
2321
|
if (metrics.length === 0) {
|
|
2220
|
-
console.log(
|
|
2322
|
+
console.log(chalk19.dim(" Nothing metered in this period yet."));
|
|
2221
2323
|
return;
|
|
2222
2324
|
}
|
|
2223
2325
|
for (const m of metrics) {
|
|
2224
|
-
console.log(` ${
|
|
2326
|
+
console.log(` ${chalk19.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
|
|
2225
2327
|
}
|
|
2226
2328
|
}
|
|
2227
2329
|
function round(n) {
|
|
@@ -2236,8 +2338,9 @@ function period(start, end) {
|
|
|
2236
2338
|
}
|
|
2237
2339
|
|
|
2238
2340
|
// src/index.ts
|
|
2341
|
+
installPromptCrashGuard();
|
|
2239
2342
|
var program = new Command18();
|
|
2240
|
-
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.1
|
|
2343
|
+
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.1");
|
|
2241
2344
|
program.addCommand(loginCommand);
|
|
2242
2345
|
program.addCommand(logoutCommand);
|
|
2243
2346
|
program.addCommand(initCommand);
|