@videan/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +262 -186
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command18 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/login.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -120,8 +120,8 @@ var CloudClient = class {
|
|
|
120
120
|
const res = await this.request("GET", "/plans");
|
|
121
121
|
return res.data;
|
|
122
122
|
}
|
|
123
|
-
async request(method,
|
|
124
|
-
const url = `${this.baseUrl}${
|
|
123
|
+
async request(method, path6, body) {
|
|
124
|
+
const url = `${this.baseUrl}${path6}`;
|
|
125
125
|
const headers = {
|
|
126
126
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
127
127
|
"Content-Type": "application/json"
|
|
@@ -290,8 +290,8 @@ var AppsNamespace = class {
|
|
|
290
290
|
if (opts?.instance) params.set("instance", opts.instance);
|
|
291
291
|
if (opts?.nextToken) params.set("next_token", opts.nextToken);
|
|
292
292
|
const query = params.toString();
|
|
293
|
-
const
|
|
294
|
-
const res = await this.client.request("GET",
|
|
293
|
+
const path6 = `/apps/${appId}/logs${query ? `?${query}` : ""}`;
|
|
294
|
+
const res = await this.client.request("GET", path6);
|
|
295
295
|
return res.data;
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
@@ -1210,19 +1210,94 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1210
1210
|
}
|
|
1211
1211
|
});
|
|
1212
1212
|
|
|
1213
|
-
// src/commands/
|
|
1213
|
+
// src/commands/pull.ts
|
|
1214
1214
|
import { Command as Command5 } from "commander";
|
|
1215
1215
|
import chalk6 from "chalk";
|
|
1216
|
-
|
|
1216
|
+
import ora3 from "ora";
|
|
1217
|
+
import fs4 from "fs-extra";
|
|
1218
|
+
import path4 from "node:path";
|
|
1219
|
+
var pullCommand = new Command5("pull").description("Download a snapshot's files into a directory").argument("[snapshot]", "Snapshot id (defaults to the newest)").option("-a, --app <id>", "App ID to pull from").option("-o, --out <dir>", "Directory to write into", ".").option("--force", "Write into a non-empty directory").action(async (snapshotArg, opts) => {
|
|
1220
|
+
const apiKey = getApiKey();
|
|
1221
|
+
requireApiKey(apiKey);
|
|
1222
|
+
const baseUrl = getBaseUrl();
|
|
1223
|
+
const get = async (p) => {
|
|
1224
|
+
const res = await fetch(`${baseUrl}${p}`, {
|
|
1225
|
+
headers: { authorization: `Bearer ${apiKey}` }
|
|
1226
|
+
});
|
|
1227
|
+
if (!res.ok) {
|
|
1228
|
+
const body = await res.json().catch(() => null);
|
|
1229
|
+
throw new Error(body?.error?.message || `${res.status} from ${p}`);
|
|
1230
|
+
}
|
|
1231
|
+
return (await res.json()).data;
|
|
1232
|
+
};
|
|
1233
|
+
const spinner = ora3("Resolving snapshot...").start();
|
|
1234
|
+
try {
|
|
1235
|
+
let snapshotId = snapshotArg ?? "";
|
|
1236
|
+
if (!snapshotId) {
|
|
1237
|
+
const appId = opts.app || getCurrentApp();
|
|
1238
|
+
if (!appId) {
|
|
1239
|
+
spinner.fail(chalk6.red("No app specified. Use --app, VIDEAN_APP_ID, or name a snapshot id."));
|
|
1240
|
+
process.exit(1);
|
|
1241
|
+
}
|
|
1242
|
+
const [latest] = await get(
|
|
1243
|
+
`/v1/apps/${appId}/snapshots?limit=1`
|
|
1244
|
+
);
|
|
1245
|
+
if (!latest) {
|
|
1246
|
+
spinner.fail(chalk6.red("This app has no snapshots yet."));
|
|
1247
|
+
process.exit(1);
|
|
1248
|
+
}
|
|
1249
|
+
snapshotId = latest.id;
|
|
1250
|
+
}
|
|
1251
|
+
const detail = await get(
|
|
1252
|
+
`/v1/snapshots/${snapshotId}`
|
|
1253
|
+
);
|
|
1254
|
+
const outDir = path4.resolve(opts.out);
|
|
1255
|
+
await fs4.ensureDir(outDir);
|
|
1256
|
+
const existing = await fs4.readdir(outDir);
|
|
1257
|
+
if (existing.length > 0 && !opts.force) {
|
|
1258
|
+
spinner.fail(chalk6.red(`${outDir} is not empty. Pass --force to write into it anyway.`));
|
|
1259
|
+
process.exit(1);
|
|
1260
|
+
}
|
|
1261
|
+
spinner.text = `Pulling ${detail.manifest.length} files...`;
|
|
1262
|
+
let written = 0;
|
|
1263
|
+
for (const file of detail.manifest) {
|
|
1264
|
+
const body = await get(
|
|
1265
|
+
`/v1/snapshots/${snapshotId}/files/${file}`
|
|
1266
|
+
);
|
|
1267
|
+
const target = path4.join(outDir, file);
|
|
1268
|
+
if (!target.startsWith(outDir + path4.sep) && target !== outDir) continue;
|
|
1269
|
+
await fs4.ensureDir(path4.dirname(target));
|
|
1270
|
+
await fs4.writeFile(
|
|
1271
|
+
target,
|
|
1272
|
+
body.encoding === "base64" ? Buffer.from(body.content, "base64") : body.content
|
|
1273
|
+
);
|
|
1274
|
+
written++;
|
|
1275
|
+
spinner.text = `Pulling ${written}/${detail.manifest.length}...`;
|
|
1276
|
+
}
|
|
1277
|
+
spinner.succeed(
|
|
1278
|
+
chalk6.green(
|
|
1279
|
+
`Pulled snapshot ${detail.seq ? `#${detail.seq}` : snapshotId} \u2014 ${written} files into ${outDir}`
|
|
1280
|
+
)
|
|
1281
|
+
);
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
spinner.fail(chalk6.red(err instanceof Error ? err.message : String(err)));
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// src/commands/logs.ts
|
|
1289
|
+
import { Command as Command6 } from "commander";
|
|
1290
|
+
import chalk7 from "chalk";
|
|
1291
|
+
var logsCommand = new Command6("logs").description("View app logs").option("-a, --app <id>", "App ID").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Number of lines to show", "50").option("-s, --since <when>", "Start of the window: ISO timestamp or an offset like 15m, 2h, 7d", "1h").option("--until <when>", "End of the window (same formats as --since)").option("-q, --search <text>", "Only lines containing this text").option("--pattern <pattern>", "CloudWatch filter pattern, for the real syntax").option("--instance <prefix>", "Only one instance, by log stream prefix").action(async (opts) => {
|
|
1217
1292
|
const apiKey = getApiKey();
|
|
1218
1293
|
requireApiKey(apiKey);
|
|
1219
1294
|
const appId = opts.app || getCurrentApp();
|
|
1220
1295
|
if (!appId) {
|
|
1221
|
-
console.error(
|
|
1296
|
+
console.error(chalk7.red("No app specified. Use --app or link an app."));
|
|
1222
1297
|
process.exit(1);
|
|
1223
1298
|
}
|
|
1224
1299
|
if (opts.search && opts.pattern) {
|
|
1225
|
-
console.error(
|
|
1300
|
+
console.error(chalk7.red("Use either --search or --pattern, not both."));
|
|
1226
1301
|
process.exit(1);
|
|
1227
1302
|
}
|
|
1228
1303
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1245,43 +1320,43 @@ var logsCommand = new Command5("logs").description("View app logs").option("-a,
|
|
|
1245
1320
|
});
|
|
1246
1321
|
for (const entry of page.events) print(entry);
|
|
1247
1322
|
if (page.events.length === 0) {
|
|
1248
|
-
console.log(
|
|
1323
|
+
console.log(chalk7.gray(`No log lines since ${page.start}.`));
|
|
1249
1324
|
}
|
|
1250
1325
|
if (page.truncated) {
|
|
1251
1326
|
console.log(
|
|
1252
|
-
|
|
1327
|
+
chalk7.yellow(
|
|
1253
1328
|
"\nThis is part of the window -- there are more lines than one request reads. Narrow --since or add --search."
|
|
1254
1329
|
)
|
|
1255
1330
|
);
|
|
1256
1331
|
}
|
|
1257
1332
|
});
|
|
1258
1333
|
function print(entry) {
|
|
1259
|
-
console.log(`${
|
|
1334
|
+
console.log(`${chalk7.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
|
|
1260
1335
|
}
|
|
1261
1336
|
function formatLevel(level) {
|
|
1262
1337
|
switch (level) {
|
|
1263
1338
|
case "error":
|
|
1264
|
-
return
|
|
1339
|
+
return chalk7.red("[ERR]");
|
|
1265
1340
|
case "warn":
|
|
1266
|
-
return
|
|
1341
|
+
return chalk7.yellow("[WRN]");
|
|
1267
1342
|
case "info":
|
|
1268
|
-
return
|
|
1343
|
+
return chalk7.blue("[INF]");
|
|
1269
1344
|
case "debug":
|
|
1270
|
-
return
|
|
1345
|
+
return chalk7.gray("[DBG]");
|
|
1271
1346
|
default:
|
|
1272
|
-
return
|
|
1347
|
+
return chalk7.gray("[LOG]");
|
|
1273
1348
|
}
|
|
1274
1349
|
}
|
|
1275
1350
|
|
|
1276
1351
|
// src/commands/metrics.ts
|
|
1277
|
-
import { Command as
|
|
1278
|
-
import
|
|
1279
|
-
var metricsCommand = new
|
|
1352
|
+
import { Command as Command7 } from "commander";
|
|
1353
|
+
import chalk8 from "chalk";
|
|
1354
|
+
var metricsCommand = new Command7("metrics").description("Traffic, latency and resource use for an app").option("-a, --app <id>", "App ID").option("-s, --since <when>", "Start of the window: ISO timestamp or an offset like 15m, 2h, 7d", "1h").option("--until <when>", "End of the window (same formats as --since)").option("--series", "Print each bucket instead of just the summary").option("--json", "Print the raw response").action(async (opts) => {
|
|
1280
1355
|
const apiKey = getApiKey();
|
|
1281
1356
|
requireApiKey(apiKey);
|
|
1282
1357
|
const appId = opts.app || getCurrentApp();
|
|
1283
1358
|
if (!appId) {
|
|
1284
|
-
console.error(
|
|
1359
|
+
console.error(chalk8.red("No app specified. Use --app or link an app."));
|
|
1285
1360
|
process.exit(1);
|
|
1286
1361
|
}
|
|
1287
1362
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1292,7 +1367,7 @@ var metricsCommand = new Command6("metrics").description("Traffic, latency and r
|
|
|
1292
1367
|
}
|
|
1293
1368
|
const t = metrics.totals;
|
|
1294
1369
|
console.log(
|
|
1295
|
-
|
|
1370
|
+
chalk8.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
|
|
1296
1371
|
);
|
|
1297
1372
|
console.log();
|
|
1298
1373
|
console.log(` ${label("Requests")} ${t.requests}`);
|
|
@@ -1300,76 +1375,76 @@ var metricsCommand = new Command6("metrics").description("Traffic, latency and r
|
|
|
1300
1375
|
console.log(` ${label("5xx")} ${count(t.serverErrors, true)}`);
|
|
1301
1376
|
console.log(` ${label("Gate denied")} ${count(t.gateDenied)}`);
|
|
1302
1377
|
console.log(` ${label("Latency avg")} ${ms(t.latencyAvgMs)}`);
|
|
1303
|
-
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${
|
|
1378
|
+
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk8.dim("(sampled)")}`);
|
|
1304
1379
|
console.log(` ${label("CPU avg")} ${units(t.cpuAvg)}`);
|
|
1305
1380
|
console.log(` ${label("Memory avg")} ${mb(t.memoryAvgMb)}`);
|
|
1306
1381
|
if (t.requests === 0) {
|
|
1307
1382
|
console.log();
|
|
1308
|
-
console.log(
|
|
1383
|
+
console.log(chalk8.dim("No requests recorded in this window."));
|
|
1309
1384
|
}
|
|
1310
1385
|
if (opts.series) {
|
|
1311
1386
|
console.log();
|
|
1312
1387
|
for (const [name, points] of Object.entries(metrics.series)) {
|
|
1313
1388
|
if (points.length === 0) continue;
|
|
1314
|
-
console.log(
|
|
1315
|
-
for (const p of points) console.log(` ${
|
|
1389
|
+
console.log(chalk8.bold(name));
|
|
1390
|
+
for (const p of points) console.log(` ${chalk8.dim(p.t)} ${p.v}`);
|
|
1316
1391
|
}
|
|
1317
1392
|
}
|
|
1318
1393
|
});
|
|
1319
1394
|
function label(text) {
|
|
1320
|
-
return
|
|
1395
|
+
return chalk8.dim(text.padEnd(13));
|
|
1321
1396
|
}
|
|
1322
1397
|
function count(n, severe = false) {
|
|
1323
|
-
if (n === 0) return
|
|
1324
|
-
return severe ?
|
|
1398
|
+
if (n === 0) return chalk8.dim("0");
|
|
1399
|
+
return severe ? chalk8.red(String(n)) : chalk8.yellow(String(n));
|
|
1325
1400
|
}
|
|
1326
1401
|
function ms(v) {
|
|
1327
|
-
return v === null ?
|
|
1402
|
+
return v === null ? chalk8.dim("\u2014") : `${v} ms`;
|
|
1328
1403
|
}
|
|
1329
1404
|
function mb(v) {
|
|
1330
|
-
return v === null ?
|
|
1405
|
+
return v === null ? chalk8.dim("\u2014") : `${v} MB`;
|
|
1331
1406
|
}
|
|
1332
1407
|
function units(v) {
|
|
1333
|
-
return v === null ?
|
|
1408
|
+
return v === null ? chalk8.dim("\u2014") : `${v} CPU units`;
|
|
1334
1409
|
}
|
|
1335
1410
|
|
|
1336
1411
|
// src/commands/rollback.ts
|
|
1337
|
-
import { Command as
|
|
1338
|
-
import
|
|
1339
|
-
import
|
|
1340
|
-
var rollbackCommand = new
|
|
1412
|
+
import { Command as Command8 } from "commander";
|
|
1413
|
+
import chalk9 from "chalk";
|
|
1414
|
+
import ora4 from "ora";
|
|
1415
|
+
var rollbackCommand = new Command8("rollback").description("Rollback to the previous production deployment").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1341
1416
|
const apiKey = getApiKey();
|
|
1342
1417
|
requireApiKey(apiKey);
|
|
1343
1418
|
const appId = opts.app || getCurrentApp();
|
|
1344
1419
|
if (!appId) {
|
|
1345
|
-
console.error(
|
|
1420
|
+
console.error(chalk9.red("No app specified. Use --app or link an app."));
|
|
1346
1421
|
process.exit(1);
|
|
1347
1422
|
}
|
|
1348
1423
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1349
|
-
const spinner =
|
|
1424
|
+
const spinner = ora4("Rolling back...").start();
|
|
1350
1425
|
try {
|
|
1351
1426
|
const deployment = await client4.apps.rollback(appId);
|
|
1352
|
-
spinner.succeed(
|
|
1427
|
+
spinner.succeed(chalk9.green("Rollback successful!"));
|
|
1353
1428
|
if (deployment.url) {
|
|
1354
|
-
console.log(
|
|
1429
|
+
console.log(chalk9.cyan(` URL: ${deployment.url}`));
|
|
1355
1430
|
}
|
|
1356
1431
|
} catch (error) {
|
|
1357
|
-
spinner.fail(
|
|
1432
|
+
spinner.fail(chalk9.red(`Rollback failed: ${error.message}`));
|
|
1358
1433
|
process.exit(1);
|
|
1359
1434
|
}
|
|
1360
1435
|
});
|
|
1361
1436
|
|
|
1362
1437
|
// src/commands/secrets.ts
|
|
1363
|
-
import { Command as
|
|
1364
|
-
import
|
|
1438
|
+
import { Command as Command9 } from "commander";
|
|
1439
|
+
import chalk10 from "chalk";
|
|
1365
1440
|
import inquirer3 from "inquirer";
|
|
1366
|
-
var secretsCommand = new
|
|
1441
|
+
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1367
1442
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1368
1443
|
const apiKey = getApiKey();
|
|
1369
1444
|
requireApiKey(apiKey);
|
|
1370
1445
|
const appId = opts.app || getCurrentApp();
|
|
1371
1446
|
if (!appId) {
|
|
1372
|
-
console.error(
|
|
1447
|
+
console.error(chalk10.red("No app specified."));
|
|
1373
1448
|
process.exit(1);
|
|
1374
1449
|
}
|
|
1375
1450
|
if (!value) {
|
|
@@ -1380,20 +1455,20 @@ secretsCommand.command("set <key> [value]").description("Set a secret").option("
|
|
|
1380
1455
|
}
|
|
1381
1456
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1382
1457
|
await client4.secrets.set(appId, key, value);
|
|
1383
|
-
console.log(
|
|
1458
|
+
console.log(chalk10.green(`Secret "${key}" set successfully.`));
|
|
1384
1459
|
});
|
|
1385
1460
|
secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1386
1461
|
const apiKey = getApiKey();
|
|
1387
1462
|
requireApiKey(apiKey);
|
|
1388
1463
|
const appId = opts.app || getCurrentApp();
|
|
1389
1464
|
if (!appId) {
|
|
1390
|
-
console.error(
|
|
1465
|
+
console.error(chalk10.red("No app specified."));
|
|
1391
1466
|
process.exit(1);
|
|
1392
1467
|
}
|
|
1393
1468
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1394
1469
|
const secrets = await client4.secrets.list(appId);
|
|
1395
1470
|
if (secrets.length === 0) {
|
|
1396
|
-
console.log(
|
|
1471
|
+
console.log(chalk10.gray("No secrets configured."));
|
|
1397
1472
|
return;
|
|
1398
1473
|
}
|
|
1399
1474
|
console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
|
|
@@ -1406,24 +1481,24 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
|
|
|
1406
1481
|
requireApiKey(apiKey);
|
|
1407
1482
|
const appId = opts.app || getCurrentApp();
|
|
1408
1483
|
if (!appId) {
|
|
1409
|
-
console.error(
|
|
1484
|
+
console.error(chalk10.red("No app specified."));
|
|
1410
1485
|
process.exit(1);
|
|
1411
1486
|
}
|
|
1412
1487
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1413
1488
|
await client4.secrets.delete(appId, key);
|
|
1414
|
-
console.log(
|
|
1489
|
+
console.log(chalk10.green(`Secret "${key}" removed.`));
|
|
1415
1490
|
});
|
|
1416
1491
|
|
|
1417
1492
|
// src/commands/apps.ts
|
|
1418
|
-
import { Command as
|
|
1419
|
-
import
|
|
1493
|
+
import { Command as Command10 } from "commander";
|
|
1494
|
+
import chalk11 from "chalk";
|
|
1420
1495
|
import inquirer4 from "inquirer";
|
|
1421
|
-
var appsCommand = new
|
|
1496
|
+
var appsCommand = new Command10("apps").description("Manage apps");
|
|
1422
1497
|
function accessOption(value) {
|
|
1423
1498
|
if (value === void 0) return void 0;
|
|
1424
1499
|
const tier = value.trim().toLowerCase();
|
|
1425
1500
|
if (!isVisibility(tier)) {
|
|
1426
|
-
console.error(
|
|
1501
|
+
console.error(chalk11.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
|
|
1427
1502
|
process.exit(1);
|
|
1428
1503
|
}
|
|
1429
1504
|
return tier;
|
|
@@ -1439,7 +1514,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1439
1514
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1440
1515
|
const apps = await client4.apps.list(orgId);
|
|
1441
1516
|
if (apps.length === 0) {
|
|
1442
|
-
console.log(
|
|
1517
|
+
console.log(chalk11.gray("No apps yet. Create one with `videan apps create`."));
|
|
1443
1518
|
return;
|
|
1444
1519
|
}
|
|
1445
1520
|
const widths = [20, 24, 10, 44];
|
|
@@ -1461,7 +1536,7 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1461
1536
|
name = answers.name;
|
|
1462
1537
|
}
|
|
1463
1538
|
if (!name) {
|
|
1464
|
-
console.error(
|
|
1539
|
+
console.error(chalk11.red("An app name is required."));
|
|
1465
1540
|
process.exit(1);
|
|
1466
1541
|
}
|
|
1467
1542
|
const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
|
|
@@ -1475,10 +1550,10 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1475
1550
|
visibility
|
|
1476
1551
|
});
|
|
1477
1552
|
setCurrentApp(app.id);
|
|
1478
|
-
console.log(
|
|
1479
|
-
console.log(
|
|
1480
|
-
console.log(
|
|
1481
|
-
console.log(
|
|
1553
|
+
console.log(chalk11.green(`App "${app.name}" created (${app.id})`));
|
|
1554
|
+
console.log(chalk11.gray(` Access: ${describeAccess(app)}`));
|
|
1555
|
+
console.log(chalk11.gray(` URL after first deploy: ${appUrl(app.slug)}`));
|
|
1556
|
+
console.log(chalk11.gray(" Push code with `videan deploy` \u2014 the runtime is auto-detected."));
|
|
1482
1557
|
}
|
|
1483
1558
|
);
|
|
1484
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(
|
|
@@ -1487,16 +1562,16 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1487
1562
|
requireApiKey(apiKey);
|
|
1488
1563
|
const appId = opts.app || getCurrentApp();
|
|
1489
1564
|
if (!appId) {
|
|
1490
|
-
console.error(
|
|
1565
|
+
console.error(chalk11.red("No app specified. Use --app or link an app."));
|
|
1491
1566
|
process.exit(1);
|
|
1492
1567
|
}
|
|
1493
1568
|
if (opts.public && opts.private) {
|
|
1494
|
-
console.error(
|
|
1569
|
+
console.error(chalk11.red("--public and --private contradict each other."));
|
|
1495
1570
|
process.exit(1);
|
|
1496
1571
|
}
|
|
1497
1572
|
const access = accessOption(opts.access);
|
|
1498
1573
|
if (access && (opts.public || opts.private)) {
|
|
1499
|
-
console.error(
|
|
1574
|
+
console.error(chalk11.red("--access already says who may open the app; drop --public/--private."));
|
|
1500
1575
|
process.exit(1);
|
|
1501
1576
|
}
|
|
1502
1577
|
const changes = {};
|
|
@@ -1517,7 +1592,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1517
1592
|
if (opts.storage) changes.storage = true;
|
|
1518
1593
|
if (opts.email) changes.email = true;
|
|
1519
1594
|
if (Object.keys(changes).length === 0) {
|
|
1520
|
-
console.error(
|
|
1595
|
+
console.error(chalk11.red("Nothing to change. Pass at least one option."));
|
|
1521
1596
|
process.exit(1);
|
|
1522
1597
|
}
|
|
1523
1598
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1526,7 +1601,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1526
1601
|
console.log(JSON.stringify(app, null, 2));
|
|
1527
1602
|
return;
|
|
1528
1603
|
}
|
|
1529
|
-
console.log(
|
|
1604
|
+
console.log(chalk11.green(`Updated ${app.name} (${app.id})`));
|
|
1530
1605
|
console.log(` Access: ${describeAccess(app)}`);
|
|
1531
1606
|
console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
|
|
1532
1607
|
console.log(
|
|
@@ -1534,7 +1609,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1534
1609
|
);
|
|
1535
1610
|
if (app.restartRequired) {
|
|
1536
1611
|
console.log(
|
|
1537
|
-
|
|
1612
|
+
chalk11.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
|
|
1538
1613
|
);
|
|
1539
1614
|
}
|
|
1540
1615
|
}
|
|
@@ -1542,7 +1617,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1542
1617
|
function intOption(flag, raw) {
|
|
1543
1618
|
const n = Number(raw);
|
|
1544
1619
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1545
|
-
console.error(
|
|
1620
|
+
console.error(chalk11.red(`${flag} expects a positive whole number, got "${raw}".`));
|
|
1546
1621
|
process.exit(1);
|
|
1547
1622
|
}
|
|
1548
1623
|
return n;
|
|
@@ -1561,64 +1636,64 @@ appsCommand.command("delete <id>").description("Delete an app and all its resour
|
|
|
1561
1636
|
if (!confirm) return;
|
|
1562
1637
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1563
1638
|
const { warnings } = await client4.apps.delete(id);
|
|
1564
|
-
console.log(
|
|
1639
|
+
console.log(chalk11.green("App deleted."));
|
|
1565
1640
|
if (warnings.length > 0) {
|
|
1566
|
-
console.log(
|
|
1641
|
+
console.log(chalk11.yellow("\nSome resources could not be released:"));
|
|
1567
1642
|
for (const w of warnings) {
|
|
1568
|
-
console.log(
|
|
1643
|
+
console.log(chalk11.yellow(` ${w.step}: ${w.error || "unknown error"}`));
|
|
1569
1644
|
}
|
|
1570
1645
|
}
|
|
1571
1646
|
});
|
|
1572
1647
|
|
|
1573
1648
|
// src/commands/status.ts
|
|
1574
|
-
import { Command as
|
|
1575
|
-
import
|
|
1649
|
+
import { Command as Command11 } from "commander";
|
|
1650
|
+
import chalk12 from "chalk";
|
|
1576
1651
|
function accessLine(app) {
|
|
1577
1652
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1578
1653
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
1579
1654
|
}
|
|
1580
|
-
var statusCommand = new
|
|
1655
|
+
var statusCommand = new Command11("status").description("Show current app status").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1581
1656
|
const apiKey = getApiKey();
|
|
1582
1657
|
requireApiKey(apiKey);
|
|
1583
1658
|
const appId = opts.app || getCurrentApp();
|
|
1584
1659
|
if (!appId) {
|
|
1585
|
-
console.error(
|
|
1660
|
+
console.error(chalk12.red("No app specified. Use --app or link an app."));
|
|
1586
1661
|
process.exit(1);
|
|
1587
1662
|
}
|
|
1588
1663
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1589
1664
|
const app = await client4.apps.get(appId);
|
|
1590
1665
|
const health = await client4.apps.health(appId);
|
|
1591
|
-
console.log(
|
|
1666
|
+
console.log(chalk12.bold(app.name));
|
|
1592
1667
|
console.log(` ID: ${app.id}`);
|
|
1593
1668
|
console.log(` Created: ${app.createdAt}`);
|
|
1594
1669
|
console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
|
|
1595
1670
|
console.log(` Access: ${accessLine(app)}`);
|
|
1596
1671
|
console.log(` State: ${formatStatus(health.state)}`);
|
|
1597
|
-
console.log(` Healthy: ${health.healthy ?
|
|
1598
|
-
console.log(` URL: ${
|
|
1672
|
+
console.log(` Healthy: ${health.healthy ? chalk12.green("yes") : chalk12.red("no")}`);
|
|
1673
|
+
console.log(` URL: ${chalk12.cyan(health.url || appUrl(app.slug))}`);
|
|
1599
1674
|
const task = health.task;
|
|
1600
1675
|
if (task) {
|
|
1601
1676
|
if (task.exit_code !== void 0 && task.exit_code !== null) {
|
|
1602
1677
|
const code = String(task.exit_code);
|
|
1603
|
-
console.log(` Exit code: ${task.exit_code === 0 ? code :
|
|
1678
|
+
console.log(` Exit code: ${task.exit_code === 0 ? code : chalk12.red(code)}`);
|
|
1604
1679
|
}
|
|
1605
1680
|
if (task.stopped_reason) {
|
|
1606
|
-
console.log(` Stopped: ${
|
|
1681
|
+
console.log(` Stopped: ${chalk12.yellow(task.stopped_reason)}`);
|
|
1607
1682
|
}
|
|
1608
1683
|
if (task.container_reason) {
|
|
1609
|
-
console.log(` Detail: ${
|
|
1684
|
+
console.log(` Detail: ${chalk12.yellow(task.container_reason)}`);
|
|
1610
1685
|
}
|
|
1611
1686
|
}
|
|
1612
1687
|
if (!health.healthy) {
|
|
1613
|
-
console.log(
|
|
1688
|
+
console.log(chalk12.gray("\nRun `videan logs` to see why."));
|
|
1614
1689
|
}
|
|
1615
1690
|
});
|
|
1616
1691
|
|
|
1617
1692
|
// src/commands/keys.ts
|
|
1618
|
-
import { Command as
|
|
1619
|
-
import
|
|
1693
|
+
import { Command as Command12 } from "commander";
|
|
1694
|
+
import chalk13 from "chalk";
|
|
1620
1695
|
import inquirer5 from "inquirer";
|
|
1621
|
-
var keysCommand = new
|
|
1696
|
+
var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
|
|
1622
1697
|
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(
|
|
1623
1698
|
"--app <id>",
|
|
1624
1699
|
"Pin the key to one app (a service token, for code running inside it)"
|
|
@@ -1630,7 +1705,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1630
1705
|
if (opts.expires !== void 0) {
|
|
1631
1706
|
expiresInDays = Number(opts.expires);
|
|
1632
1707
|
if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
|
|
1633
|
-
console.error(
|
|
1708
|
+
console.error(chalk13.red("--expires must be a whole number of days, at least 1."));
|
|
1634
1709
|
process.exit(1);
|
|
1635
1710
|
}
|
|
1636
1711
|
}
|
|
@@ -1641,22 +1716,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1641
1716
|
expiresInDays,
|
|
1642
1717
|
appId: opts.app
|
|
1643
1718
|
});
|
|
1644
|
-
console.log(
|
|
1719
|
+
console.log(chalk13.green(`Created key "${key.name}" (${key.id})`));
|
|
1645
1720
|
console.log(`
|
|
1646
|
-
${
|
|
1721
|
+
${chalk13.bold(key.key)}
|
|
1647
1722
|
`);
|
|
1648
|
-
console.log(
|
|
1723
|
+
console.log(chalk13.yellow("Copy it now. It cannot be shown again."));
|
|
1649
1724
|
if (key.appId) {
|
|
1650
1725
|
console.log(
|
|
1651
|
-
|
|
1726
|
+
chalk13.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
|
|
1652
1727
|
);
|
|
1653
1728
|
} else {
|
|
1654
1729
|
console.log(
|
|
1655
|
-
|
|
1730
|
+
chalk13.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
|
|
1656
1731
|
);
|
|
1657
1732
|
}
|
|
1658
1733
|
if (key.expiresAt) {
|
|
1659
|
-
console.log(
|
|
1734
|
+
console.log(chalk13.gray(`Expires ${key.expiresAt}`));
|
|
1660
1735
|
}
|
|
1661
1736
|
});
|
|
1662
1737
|
keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
@@ -1666,13 +1741,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1666
1741
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1667
1742
|
const keys = await client4.apiKeys.list(orgId);
|
|
1668
1743
|
if (keys.length === 0) {
|
|
1669
|
-
console.log(
|
|
1744
|
+
console.log(chalk13.gray("No API keys. Create one with `videan keys create <name>`."));
|
|
1670
1745
|
return;
|
|
1671
1746
|
}
|
|
1672
1747
|
const widths = [24, 24, 16, 22, 12, 24];
|
|
1673
1748
|
console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
|
|
1674
1749
|
for (const key of keys) {
|
|
1675
|
-
const state = key.revokedAt ?
|
|
1750
|
+
const state = key.revokedAt ? chalk13.red("revoked") : key.expiresAt && new Date(key.expiresAt) < /* @__PURE__ */ new Date() ? chalk13.yellow("expired") : chalk13.green("active");
|
|
1676
1751
|
console.log(formatRow(
|
|
1677
1752
|
[
|
|
1678
1753
|
key.name,
|
|
@@ -1695,13 +1770,13 @@ keysCommand.command("revoke <id>").description("Revoke an API key").action(async
|
|
|
1695
1770
|
if (!confirm) return;
|
|
1696
1771
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1697
1772
|
await client4.apiKeys.revoke(id);
|
|
1698
|
-
console.log(
|
|
1773
|
+
console.log(chalk13.green("Key revoked."));
|
|
1699
1774
|
});
|
|
1700
1775
|
|
|
1701
1776
|
// src/commands/orgs.ts
|
|
1702
|
-
import { Command as
|
|
1703
|
-
import
|
|
1704
|
-
var orgsCommand = new
|
|
1777
|
+
import { Command as Command13 } from "commander";
|
|
1778
|
+
import chalk14 from "chalk";
|
|
1779
|
+
var orgsCommand = new Command13("orgs").description("Manage organizations");
|
|
1705
1780
|
orgsCommand.command("list").description("List your organizations").action(async () => {
|
|
1706
1781
|
const apiKey = getApiKey();
|
|
1707
1782
|
requireApiKey(apiKey);
|
|
@@ -1709,14 +1784,14 @@ orgsCommand.command("list").description("List your organizations").action(async
|
|
|
1709
1784
|
const orgs = await client4.orgs.list();
|
|
1710
1785
|
const current = getDefaultOrg();
|
|
1711
1786
|
if (orgs.length === 0) {
|
|
1712
|
-
console.log(
|
|
1787
|
+
console.log(chalk14.gray("No organizations. Create one with `videan orgs create <name>`."));
|
|
1713
1788
|
return;
|
|
1714
1789
|
}
|
|
1715
1790
|
const widths = [24, 24, 10, 8];
|
|
1716
1791
|
console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
|
|
1717
1792
|
for (const org of orgs) {
|
|
1718
1793
|
console.log(formatRow(
|
|
1719
|
-
[org.name, org.id, org.plan, org.id === current ?
|
|
1794
|
+
[org.name, org.id, org.plan, org.id === current ? chalk14.green("default") : ""],
|
|
1720
1795
|
widths
|
|
1721
1796
|
));
|
|
1722
1797
|
}
|
|
@@ -1727,7 +1802,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
|
|
|
1727
1802
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1728
1803
|
const org = await client4.orgs.create(name);
|
|
1729
1804
|
if (!getDefaultOrg()) setDefaultOrg(org.id);
|
|
1730
|
-
console.log(
|
|
1805
|
+
console.log(chalk14.green(`Organization "${org.name}" created (${org.id})`));
|
|
1731
1806
|
});
|
|
1732
1807
|
orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
|
|
1733
1808
|
const apiKey = getApiKey();
|
|
@@ -1735,7 +1810,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
|
|
|
1735
1810
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1736
1811
|
const org = await client4.orgs.get(id);
|
|
1737
1812
|
setDefaultOrg(org.id);
|
|
1738
|
-
console.log(
|
|
1813
|
+
console.log(chalk14.green(`Default organization set to "${org.name}".`));
|
|
1739
1814
|
});
|
|
1740
1815
|
orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
1741
1816
|
const apiKey = getApiKey();
|
|
@@ -1758,15 +1833,15 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1758
1833
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1759
1834
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1760
1835
|
await client4.orgs.invite(orgId, email);
|
|
1761
|
-
console.log(
|
|
1836
|
+
console.log(chalk14.green(`Invited ${email}.`));
|
|
1762
1837
|
});
|
|
1763
1838
|
|
|
1764
1839
|
// src/commands/storage.ts
|
|
1765
|
-
import { Command as
|
|
1766
|
-
import
|
|
1767
|
-
import
|
|
1768
|
-
import
|
|
1769
|
-
var storageCommand = new
|
|
1840
|
+
import { Command as Command14 } from "commander";
|
|
1841
|
+
import chalk15 from "chalk";
|
|
1842
|
+
import fs5 from "fs-extra";
|
|
1843
|
+
import path5 from "node:path";
|
|
1844
|
+
var storageCommand = new Command14("storage").description("Store files for an app");
|
|
1770
1845
|
function client() {
|
|
1771
1846
|
const apiKey = getApiKey();
|
|
1772
1847
|
requireApiKey(apiKey);
|
|
@@ -1775,7 +1850,7 @@ function client() {
|
|
|
1775
1850
|
function requireApp(opt) {
|
|
1776
1851
|
const appId = opt || getCurrentApp();
|
|
1777
1852
|
if (!appId) {
|
|
1778
|
-
console.error(
|
|
1853
|
+
console.error(chalk15.red("No app specified. Use --app or link an app."));
|
|
1779
1854
|
process.exit(1);
|
|
1780
1855
|
}
|
|
1781
1856
|
return appId;
|
|
@@ -1798,7 +1873,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1798
1873
|
return;
|
|
1799
1874
|
}
|
|
1800
1875
|
if (objects.length === 0) {
|
|
1801
|
-
console.log(
|
|
1876
|
+
console.log(chalk15.gray("No objects stored. Upload one with `videan storage put <file>`."));
|
|
1802
1877
|
return;
|
|
1803
1878
|
}
|
|
1804
1879
|
const widths = [52, 12, 24];
|
|
@@ -1807,42 +1882,42 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1807
1882
|
console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
|
|
1808
1883
|
}
|
|
1809
1884
|
const total = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1810
|
-
console.log(
|
|
1885
|
+
console.log(chalk15.dim(`
|
|
1811
1886
|
${objects.length} object(s), ${human(total)}`));
|
|
1812
1887
|
});
|
|
1813
1888
|
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) => {
|
|
1814
1889
|
const appId = requireApp(opts.app);
|
|
1815
|
-
if (!await
|
|
1816
|
-
console.error(
|
|
1890
|
+
if (!await fs5.pathExists(file)) {
|
|
1891
|
+
console.error(chalk15.red(`No such file: ${file}`));
|
|
1817
1892
|
process.exit(1);
|
|
1818
1893
|
}
|
|
1819
|
-
const body = await
|
|
1820
|
-
const objectKey = key ||
|
|
1894
|
+
const body = await fs5.readFile(file);
|
|
1895
|
+
const objectKey = key || path5.basename(file);
|
|
1821
1896
|
const stored = await client().storage.upload(appId, objectKey, body, {
|
|
1822
1897
|
contentType: opts.type
|
|
1823
1898
|
});
|
|
1824
1899
|
console.log(
|
|
1825
|
-
|
|
1900
|
+
chalk15.green(`Uploaded ${objectKey}`) + chalk15.dim(` (${human(body.length)})`)
|
|
1826
1901
|
);
|
|
1827
|
-
if (stored.etag) console.log(
|
|
1902
|
+
if (stored.etag) console.log(chalk15.dim(`etag ${stored.etag}`));
|
|
1828
1903
|
});
|
|
1829
1904
|
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) => {
|
|
1830
1905
|
const appId = requireApp(opts.app);
|
|
1831
1906
|
const bytes = await client().storage.download(appId, key);
|
|
1832
|
-
const dest = file ||
|
|
1833
|
-
await
|
|
1834
|
-
console.log(
|
|
1907
|
+
const dest = file || path5.basename(key);
|
|
1908
|
+
await fs5.writeFile(dest, bytes);
|
|
1909
|
+
console.log(chalk15.green(`Wrote ${dest}`) + chalk15.dim(` (${human(bytes.length)})`));
|
|
1835
1910
|
});
|
|
1836
1911
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
1837
1912
|
const appId = requireApp(opts.app);
|
|
1838
1913
|
await client().storage.delete(appId, key);
|
|
1839
|
-
console.log(
|
|
1914
|
+
console.log(chalk15.green(`Deleted ${key}`));
|
|
1840
1915
|
});
|
|
1841
1916
|
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) => {
|
|
1842
1917
|
const appId = requireApp(opts.app);
|
|
1843
1918
|
const expiresIn = Number(opts.expires);
|
|
1844
1919
|
if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
|
|
1845
|
-
console.error(
|
|
1920
|
+
console.error(chalk15.red("--expires must be a whole number of seconds between 30 and 3600."));
|
|
1846
1921
|
process.exit(1);
|
|
1847
1922
|
}
|
|
1848
1923
|
const signed = await client().storage.presign(appId, key, {
|
|
@@ -1852,7 +1927,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
|
|
|
1852
1927
|
});
|
|
1853
1928
|
console.log(signed.url);
|
|
1854
1929
|
console.error(
|
|
1855
|
-
|
|
1930
|
+
chalk15.yellow(
|
|
1856
1931
|
`This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
|
|
1857
1932
|
)
|
|
1858
1933
|
);
|
|
@@ -1864,10 +1939,10 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1864
1939
|
});
|
|
1865
1940
|
|
|
1866
1941
|
// src/commands/email.ts
|
|
1867
|
-
import { Command as
|
|
1868
|
-
import
|
|
1869
|
-
import
|
|
1870
|
-
var emailCommand = new
|
|
1942
|
+
import { Command as Command15 } from "commander";
|
|
1943
|
+
import chalk16 from "chalk";
|
|
1944
|
+
import fs6 from "fs-extra";
|
|
1945
|
+
var emailCommand = new Command15("email").description("Send email from an app");
|
|
1871
1946
|
function client2() {
|
|
1872
1947
|
const apiKey = getApiKey();
|
|
1873
1948
|
requireApiKey(apiKey);
|
|
@@ -1876,7 +1951,7 @@ function client2() {
|
|
|
1876
1951
|
function requireApp2(opt) {
|
|
1877
1952
|
const appId = opt || getCurrentApp();
|
|
1878
1953
|
if (!appId) {
|
|
1879
|
-
console.error(
|
|
1954
|
+
console.error(chalk16.red("No app specified. Use --app or link an app."));
|
|
1880
1955
|
process.exit(1);
|
|
1881
1956
|
}
|
|
1882
1957
|
return appId;
|
|
@@ -1887,12 +1962,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1887
1962
|
let text = opts.text;
|
|
1888
1963
|
let html = opts.html;
|
|
1889
1964
|
if (opts.bodyFile) {
|
|
1890
|
-
const contents = await
|
|
1965
|
+
const contents = await fs6.readFile(opts.bodyFile, "utf-8");
|
|
1891
1966
|
if (/\.html?$/i.test(opts.bodyFile)) html = contents;
|
|
1892
1967
|
else text = contents;
|
|
1893
1968
|
}
|
|
1894
1969
|
if (!text && !html) {
|
|
1895
|
-
console.error(
|
|
1970
|
+
console.error(chalk16.red("Nothing to send. Pass --text, --html or --body-file."));
|
|
1896
1971
|
process.exit(1);
|
|
1897
1972
|
}
|
|
1898
1973
|
const sent = await client2().email.send(appId, {
|
|
@@ -1906,12 +1981,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1906
1981
|
console.log(JSON.stringify(sent, null, 2));
|
|
1907
1982
|
return;
|
|
1908
1983
|
}
|
|
1909
|
-
console.log(
|
|
1984
|
+
console.log(chalk16.green("Sent"));
|
|
1910
1985
|
console.log(` From: ${sent.from}`);
|
|
1911
1986
|
console.log(` To: ${sent.to.join(", ")}`);
|
|
1912
1987
|
console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
|
|
1913
1988
|
console.log(
|
|
1914
|
-
|
|
1989
|
+
chalk16.dim(
|
|
1915
1990
|
` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
|
|
1916
1991
|
)
|
|
1917
1992
|
);
|
|
@@ -1926,14 +2001,14 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
1926
2001
|
console.log(`Sent today: ${quota.sent}`);
|
|
1927
2002
|
console.log(`Daily quota: ${quota.quota}`);
|
|
1928
2003
|
console.log(`Remaining: ${quota.remaining}`);
|
|
1929
|
-
console.log(
|
|
2004
|
+
console.log(chalk16.dim(`Resets at: ${quota.resetsAt}`));
|
|
1930
2005
|
});
|
|
1931
2006
|
|
|
1932
2007
|
// src/commands/jobs.ts
|
|
1933
|
-
import { Command as
|
|
1934
|
-
import
|
|
1935
|
-
import
|
|
1936
|
-
var jobsCommand = new
|
|
2008
|
+
import { Command as Command16 } from "commander";
|
|
2009
|
+
import chalk17 from "chalk";
|
|
2010
|
+
import fs7 from "fs-extra";
|
|
2011
|
+
var jobsCommand = new Command16("jobs").description("Enqueue and inspect background jobs");
|
|
1937
2012
|
function client3() {
|
|
1938
2013
|
const apiKey = getApiKey();
|
|
1939
2014
|
requireApiKey(apiKey);
|
|
@@ -1942,7 +2017,7 @@ function client3() {
|
|
|
1942
2017
|
function requireApp3(opt) {
|
|
1943
2018
|
const appId = opt || getCurrentApp();
|
|
1944
2019
|
if (!appId) {
|
|
1945
|
-
console.error(
|
|
2020
|
+
console.error(chalk17.red("No app specified. Use --app or run `videan init` first."));
|
|
1946
2021
|
process.exit(1);
|
|
1947
2022
|
}
|
|
1948
2023
|
return appId;
|
|
@@ -1950,30 +2025,30 @@ function requireApp3(opt) {
|
|
|
1950
2025
|
function intOption2(flag, raw) {
|
|
1951
2026
|
const n = Number(raw);
|
|
1952
2027
|
if (!Number.isInteger(n) || n < 0) {
|
|
1953
|
-
console.error(
|
|
2028
|
+
console.error(chalk17.red(`${flag} expects a whole number, got "${raw}".`));
|
|
1954
2029
|
process.exit(1);
|
|
1955
2030
|
}
|
|
1956
2031
|
return n;
|
|
1957
2032
|
}
|
|
1958
2033
|
var STATUS_COLOR = {
|
|
1959
|
-
queued:
|
|
1960
|
-
running:
|
|
1961
|
-
succeeded:
|
|
1962
|
-
dead:
|
|
1963
|
-
cancelled:
|
|
2034
|
+
queued: chalk17.yellow,
|
|
2035
|
+
running: chalk17.cyan,
|
|
2036
|
+
succeeded: chalk17.green,
|
|
2037
|
+
dead: chalk17.red,
|
|
2038
|
+
cancelled: chalk17.gray
|
|
1964
2039
|
};
|
|
1965
2040
|
function statusLabel(status) {
|
|
1966
|
-
return (STATUS_COLOR[status] ??
|
|
2041
|
+
return (STATUS_COLOR[status] ?? chalk17.white)(status);
|
|
1967
2042
|
}
|
|
1968
2043
|
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) => {
|
|
1969
2044
|
const appId = requireApp3(opts.app);
|
|
1970
2045
|
let payload;
|
|
1971
|
-
const raw = opts.file ? await
|
|
2046
|
+
const raw = opts.file ? await fs7.readFile(opts.file, "utf-8") : opts.data;
|
|
1972
2047
|
if (raw !== void 0) {
|
|
1973
2048
|
try {
|
|
1974
2049
|
payload = JSON.parse(raw);
|
|
1975
2050
|
} catch (e) {
|
|
1976
|
-
console.error(
|
|
2051
|
+
console.error(chalk17.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
|
|
1977
2052
|
process.exit(1);
|
|
1978
2053
|
}
|
|
1979
2054
|
}
|
|
@@ -1991,13 +2066,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
1991
2066
|
return;
|
|
1992
2067
|
}
|
|
1993
2068
|
if (job.deduplicated) {
|
|
1994
|
-
console.log(
|
|
2069
|
+
console.log(chalk17.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
|
|
1995
2070
|
} else {
|
|
1996
|
-
console.log(
|
|
2071
|
+
console.log(chalk17.green(`Queued ${job.id}`));
|
|
1997
2072
|
}
|
|
1998
|
-
console.log(
|
|
2073
|
+
console.log(chalk17.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
1999
2074
|
if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
|
|
2000
|
-
console.log(
|
|
2075
|
+
console.log(chalk17.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
|
|
2001
2076
|
}
|
|
2002
2077
|
});
|
|
2003
2078
|
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) => {
|
|
@@ -2012,17 +2087,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
|
|
|
2012
2087
|
return;
|
|
2013
2088
|
}
|
|
2014
2089
|
const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
|
|
2015
|
-
console.log(depth ||
|
|
2090
|
+
console.log(depth || chalk17.gray("No jobs yet."));
|
|
2016
2091
|
if (jobs.length === 0) return;
|
|
2017
2092
|
console.log();
|
|
2018
2093
|
for (const job of jobs) {
|
|
2019
|
-
const attempts = job.attempts > 0 ?
|
|
2094
|
+
const attempts = job.attempts > 0 ? chalk17.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
|
|
2020
2095
|
console.log(
|
|
2021
|
-
`${statusLabel(job.status).padEnd(20)} ${
|
|
2096
|
+
`${statusLabel(job.status).padEnd(20)} ${chalk17.white(job.id)} ${chalk17.gray(
|
|
2022
2097
|
`${job.queue} ${job.path}`
|
|
2023
2098
|
)}${attempts}`
|
|
2024
2099
|
);
|
|
2025
|
-
if (job.error) console.log(
|
|
2100
|
+
if (job.error) console.log(chalk17.red(` ${job.error}`));
|
|
2026
2101
|
}
|
|
2027
2102
|
});
|
|
2028
2103
|
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) => {
|
|
@@ -2031,17 +2106,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2031
2106
|
console.log(JSON.stringify(job, null, 2));
|
|
2032
2107
|
return;
|
|
2033
2108
|
}
|
|
2034
|
-
console.log(`${
|
|
2035
|
-
console.log(
|
|
2036
|
-
console.log(
|
|
2037
|
-
if (job.statusCode) console.log(
|
|
2038
|
-
if (job.durationMs) console.log(
|
|
2039
|
-
if (job.error) console.log(
|
|
2109
|
+
console.log(`${chalk17.white(job.id)} ${statusLabel(job.status)}`);
|
|
2110
|
+
console.log(chalk17.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2111
|
+
console.log(chalk17.gray(` Attempts: ${job.attempts}/${job.maxAttempts}`));
|
|
2112
|
+
if (job.statusCode) console.log(chalk17.gray(` Last response: HTTP ${job.statusCode}`));
|
|
2113
|
+
if (job.durationMs) console.log(chalk17.gray(` Took: ${job.durationMs}ms`));
|
|
2114
|
+
if (job.error) console.log(chalk17.red(` Error: ${job.error}`));
|
|
2040
2115
|
if (job.status === "queued" && job.attempts > 0) {
|
|
2041
|
-
console.log(
|
|
2116
|
+
console.log(chalk17.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
|
|
2042
2117
|
}
|
|
2043
2118
|
if (job.payload !== null && job.payload !== void 0) {
|
|
2044
|
-
console.log(
|
|
2119
|
+
console.log(chalk17.gray(" Payload:"));
|
|
2045
2120
|
console.log(
|
|
2046
2121
|
JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
|
|
2047
2122
|
);
|
|
@@ -2049,17 +2124,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2049
2124
|
});
|
|
2050
2125
|
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) => {
|
|
2051
2126
|
const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
|
|
2052
|
-
console.log(
|
|
2127
|
+
console.log(chalk17.green(`Requeued ${job.id}`));
|
|
2053
2128
|
});
|
|
2054
2129
|
jobsCommand.command("cancel <job-id>").description("Cancel a job that has not started yet").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
|
|
2055
2130
|
const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
|
|
2056
|
-
console.log(
|
|
2131
|
+
console.log(chalk17.green(`Cancelled ${job.id}`));
|
|
2057
2132
|
});
|
|
2058
2133
|
|
|
2059
2134
|
// src/commands/usage.ts
|
|
2060
|
-
import { Command as
|
|
2061
|
-
import
|
|
2062
|
-
var usageCommand = new
|
|
2135
|
+
import { Command as Command17 } from "commander";
|
|
2136
|
+
import chalk18 from "chalk";
|
|
2137
|
+
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) => {
|
|
2063
2138
|
const apiKey = getApiKey();
|
|
2064
2139
|
requireApiKey(apiKey);
|
|
2065
2140
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -2067,61 +2142,61 @@ var usageCommand = new Command16("usage").description("Metered usage and cost fo
|
|
|
2067
2142
|
const appId = opts.app || getCurrentApp();
|
|
2068
2143
|
const report2 = await client4.apps.usage(appId, opts.period);
|
|
2069
2144
|
if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
|
|
2070
|
-
console.log(
|
|
2145
|
+
console.log(chalk18.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
|
|
2071
2146
|
console.log();
|
|
2072
2147
|
printMetrics(report2.metrics);
|
|
2073
2148
|
console.log();
|
|
2074
2149
|
for (const line of report2.lines) {
|
|
2075
|
-
console.log(` ${
|
|
2150
|
+
console.log(` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)}`);
|
|
2076
2151
|
}
|
|
2077
|
-
console.log(` ${
|
|
2152
|
+
console.log(` ${chalk18.bold("Share of the bill".padEnd(22))} ${chalk18.bold(report2.cost)}`);
|
|
2078
2153
|
console.log();
|
|
2079
2154
|
console.log(
|
|
2080
|
-
|
|
2155
|
+
chalk18.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
|
|
2081
2156
|
);
|
|
2082
2157
|
return;
|
|
2083
2158
|
}
|
|
2084
2159
|
const orgId = opts.org || getDefaultOrg();
|
|
2085
2160
|
if (!orgId) {
|
|
2086
|
-
console.error(
|
|
2161
|
+
console.error(chalk18.red("No org specified. Use --org, or --app for a single app."));
|
|
2087
2162
|
process.exit(1);
|
|
2088
2163
|
}
|
|
2089
2164
|
const report = await client4.orgs.usage(orgId, opts.period);
|
|
2090
2165
|
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
2091
2166
|
const inv = report.invoice;
|
|
2092
2167
|
console.log(
|
|
2093
|
-
|
|
2168
|
+
chalk18.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
|
|
2094
2169
|
);
|
|
2095
2170
|
console.log();
|
|
2096
2171
|
printMetrics(report.metrics);
|
|
2097
2172
|
if (report.apps.length > 0) {
|
|
2098
2173
|
console.log();
|
|
2099
|
-
console.log(
|
|
2174
|
+
console.log(chalk18.bold("By app"));
|
|
2100
2175
|
for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
|
|
2101
2176
|
const name = app.slug || app.name || app.appId || "(deleted apps)";
|
|
2102
|
-
console.log(` ${
|
|
2177
|
+
console.log(` ${chalk18.dim(String(name).padEnd(24))} ${money(app.cents)}`);
|
|
2103
2178
|
}
|
|
2104
2179
|
}
|
|
2105
2180
|
console.log();
|
|
2106
2181
|
for (const line of inv.lines) {
|
|
2107
2182
|
console.log(
|
|
2108
|
-
` ${
|
|
2183
|
+
` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk18.dim(
|
|
2109
2184
|
`(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
|
|
2110
2185
|
)}`
|
|
2111
2186
|
);
|
|
2112
2187
|
}
|
|
2113
|
-
console.log(` ${
|
|
2188
|
+
console.log(` ${chalk18.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
|
|
2114
2189
|
if (inv.includedUsageCents > 0) {
|
|
2115
2190
|
console.log(
|
|
2116
|
-
` ${
|
|
2191
|
+
` ${chalk18.dim("Included".padEnd(22))} ${chalk18.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
|
|
2117
2192
|
);
|
|
2118
2193
|
}
|
|
2119
2194
|
if (inv.baseCents > 0) {
|
|
2120
|
-
console.log(` ${
|
|
2195
|
+
console.log(` ${chalk18.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
|
|
2121
2196
|
}
|
|
2122
|
-
console.log(` ${
|
|
2197
|
+
console.log(` ${chalk18.bold("Total".padEnd(22))} ${chalk18.bold(report.total)}`);
|
|
2123
2198
|
console.log();
|
|
2124
|
-
console.log(
|
|
2199
|
+
console.log(chalk18.dim("Usage to date. The period is priced as it stands right now."));
|
|
2125
2200
|
});
|
|
2126
2201
|
usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
|
|
2127
2202
|
const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
|
|
@@ -2129,11 +2204,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2129
2204
|
if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
|
|
2130
2205
|
for (const plan of plans) {
|
|
2131
2206
|
console.log(
|
|
2132
|
-
`${
|
|
2207
|
+
`${chalk18.bold(plan.name)} ${chalk18.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk18.dim(`, ${plan.includedUsage} usage included`) : "")
|
|
2133
2208
|
);
|
|
2134
2209
|
const l = plan.limits;
|
|
2135
2210
|
console.log(
|
|
2136
|
-
|
|
2211
|
+
chalk18.dim(
|
|
2137
2212
|
` ${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" : ""}`
|
|
2138
2213
|
)
|
|
2139
2214
|
);
|
|
@@ -2142,11 +2217,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2142
2217
|
});
|
|
2143
2218
|
function printMetrics(metrics) {
|
|
2144
2219
|
if (metrics.length === 0) {
|
|
2145
|
-
console.log(
|
|
2220
|
+
console.log(chalk18.dim(" Nothing metered in this period yet."));
|
|
2146
2221
|
return;
|
|
2147
2222
|
}
|
|
2148
2223
|
for (const m of metrics) {
|
|
2149
|
-
console.log(` ${
|
|
2224
|
+
console.log(` ${chalk18.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
|
|
2150
2225
|
}
|
|
2151
2226
|
}
|
|
2152
2227
|
function round(n) {
|
|
@@ -2161,12 +2236,13 @@ function period(start, end) {
|
|
|
2161
2236
|
}
|
|
2162
2237
|
|
|
2163
2238
|
// src/index.ts
|
|
2164
|
-
var program = new
|
|
2239
|
+
var program = new Command18();
|
|
2165
2240
|
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.1.0");
|
|
2166
2241
|
program.addCommand(loginCommand);
|
|
2167
2242
|
program.addCommand(logoutCommand);
|
|
2168
2243
|
program.addCommand(initCommand);
|
|
2169
2244
|
program.addCommand(deployCommand);
|
|
2245
|
+
program.addCommand(pullCommand);
|
|
2170
2246
|
program.addCommand(logsCommand);
|
|
2171
2247
|
program.addCommand(metricsCommand);
|
|
2172
2248
|
program.addCommand(rollbackCommand);
|