@videan/cli 0.1.1 → 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 +375 -224
- 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
|
/**
|
|
@@ -934,35 +934,100 @@ var logoutCommand = new Command2("logout").description("Forget the saved key, or
|
|
|
934
934
|
import { Command as Command3 } from "commander";
|
|
935
935
|
import inquirer2 from "inquirer";
|
|
936
936
|
import chalk3 from "chalk";
|
|
937
|
+
import fs2 from "fs-extra";
|
|
938
|
+
import path2 from "node:path";
|
|
939
|
+
|
|
940
|
+
// src/videan-md.ts
|
|
937
941
|
import fs from "fs-extra";
|
|
938
942
|
import path from "node:path";
|
|
939
|
-
var
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
943
|
+
var VIDEAN_MD = `# This project deploys to Videan
|
|
944
|
+
|
|
945
|
+
Deploy: \`videan deploy\` (from this directory). Logs: \`videan logs -f\`.
|
|
946
|
+
Undo a bad deploy: \`videan rollback\`. Full agent guide: https://videan.xyz/llms.txt
|
|
947
|
+
|
|
948
|
+
The contract: serve HTTP on **port 3000**. Runtime (Node/Python/Next.js/Go/
|
|
949
|
+
Dockerfile) is detected from the files \u2014 no config needed. If the app has a
|
|
950
|
+
managed database, read \`DATABASE_URL\` from the environment. Log to stdout.
|
|
951
|
+
|
|
952
|
+
## Auth \u2014 read this before writing any login code
|
|
953
|
+
|
|
954
|
+
This platform puts a sign-in gate in front of apps unless they are public.
|
|
955
|
+
|
|
956
|
+
- **Gated app (visibility private/org/authenticated \u2014 the default): DO NOT
|
|
957
|
+
build a login page.** Every request arrives with the visitor already
|
|
958
|
+
authenticated: read \`X-User-Id\`, \`X-User-Email\`, \`X-User-Roles\`. If the
|
|
959
|
+
app needs its own user records, find-or-create a local user from
|
|
960
|
+
\`X-User-Email\` and start your session from that \u2014 never render a login
|
|
961
|
+
form when the header is present.
|
|
962
|
+
- **Public app** (\`videan apps set --public\`): the gate is out of the path,
|
|
963
|
+
no headers arrive, bring your own auth if the app needs any. Required for
|
|
964
|
+
anything that must receive webhooks from external services.
|
|
965
|
+
|
|
966
|
+
Secrets: \`videan secrets set KEY=value\` (UPPERCASE keys) \u2014 injected as env
|
|
967
|
+
vars on the next deploy. Never commit credentials to this repo instead.
|
|
968
|
+
`;
|
|
969
|
+
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
|
+
async function writeVideanMd(dir) {
|
|
971
|
+
const wrote = [];
|
|
972
|
+
const target = path.join(dir, "VIDEAN.md");
|
|
973
|
+
if (!await fs.pathExists(target)) {
|
|
974
|
+
await fs.writeFile(target, VIDEAN_MD, "utf-8");
|
|
975
|
+
wrote.push("VIDEAN.md");
|
|
976
|
+
}
|
|
977
|
+
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
978
|
+
const p = path.join(dir, name);
|
|
979
|
+
if (!await fs.pathExists(p)) continue;
|
|
980
|
+
const body = await fs.readFile(p, "utf-8");
|
|
981
|
+
if (body.includes("VIDEAN.md")) continue;
|
|
982
|
+
await fs.appendFile(p, POINTER, "utf-8");
|
|
983
|
+
wrote.push(`${name} (pointer)`);
|
|
984
|
+
}
|
|
985
|
+
return wrote;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/commands/init.ts
|
|
989
|
+
var initCommand = new Command3("init").description("Initialize a new Cloud app in the current directory").option("--name <name>", "App name (defaults to the directory name)").option("--runtime <runtime>", "One of: " + SUPPORTED_RUNTIMES.join(", ")).option("--entrypoint <file>", "Entry file", "index.ts").option("--yes", "Take the defaults without prompting").action(async (opts) => {
|
|
990
|
+
const configPath2 = path2.join(process.cwd(), CONFIG_FILE_NAME);
|
|
991
|
+
const interactive = process.stdin.isTTY && !opts.yes;
|
|
992
|
+
if (await fs2.pathExists(configPath2)) {
|
|
993
|
+
if (!interactive) {
|
|
994
|
+
console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
|
|
995
|
+
} else {
|
|
996
|
+
console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
|
|
997
|
+
const { overwrite } = await inquirer2.prompt([
|
|
998
|
+
{ type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
|
|
999
|
+
]);
|
|
1000
|
+
if (!overwrite) {
|
|
1001
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
947
1005
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1006
|
+
if (!await fs2.pathExists(configPath2) || interactive) {
|
|
1007
|
+
const answers = interactive ? await inquirer2.prompt([
|
|
1008
|
+
{ type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
|
|
1009
|
+
{
|
|
1010
|
+
type: "list",
|
|
1011
|
+
name: "runtime",
|
|
1012
|
+
message: "Runtime:",
|
|
1013
|
+
choices: SUPPORTED_RUNTIMES
|
|
1014
|
+
},
|
|
1015
|
+
{ type: "input", name: "entrypoint", message: "Entrypoint:", default: opts.entrypoint }
|
|
1016
|
+
]) : {
|
|
1017
|
+
name: opts.name || path2.basename(process.cwd()),
|
|
1018
|
+
runtime: opts.runtime || SUPPORTED_RUNTIMES[0],
|
|
1019
|
+
entrypoint: opts.entrypoint
|
|
1020
|
+
};
|
|
1021
|
+
const config2 = [
|
|
1022
|
+
`name: ${answers.name}`,
|
|
1023
|
+
`runtime: ${answers.runtime}`,
|
|
1024
|
+
`entrypoint: ${answers.entrypoint}`,
|
|
1025
|
+
""
|
|
1026
|
+
].join("\n");
|
|
1027
|
+
await fs2.writeFile(configPath2, config2, "utf-8");
|
|
1028
|
+
console.log(chalk3.green(`Created ${CONFIG_FILE_NAME}`));
|
|
1029
|
+
}
|
|
1030
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
|
|
966
1031
|
});
|
|
967
1032
|
|
|
968
1033
|
// src/commands/deploy.ts
|
|
@@ -971,8 +1036,8 @@ import chalk5 from "chalk";
|
|
|
971
1036
|
import ora2 from "ora";
|
|
972
1037
|
|
|
973
1038
|
// src/util.ts
|
|
974
|
-
import
|
|
975
|
-
import
|
|
1039
|
+
import fs3 from "fs-extra";
|
|
1040
|
+
import path3 from "node:path";
|
|
976
1041
|
import ignore from "ignore";
|
|
977
1042
|
import chalk4 from "chalk";
|
|
978
1043
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -989,30 +1054,30 @@ async function collectFiles(dir) {
|
|
|
989
1054
|
".DS_Store",
|
|
990
1055
|
"*.log"
|
|
991
1056
|
]);
|
|
992
|
-
const gitignorePath =
|
|
993
|
-
if (await
|
|
994
|
-
const content = await
|
|
1057
|
+
const gitignorePath = path3.join(dir, ".gitignore");
|
|
1058
|
+
if (await fs3.pathExists(gitignorePath)) {
|
|
1059
|
+
const content = await fs3.readFile(gitignorePath, "utf-8");
|
|
995
1060
|
ig.add(content);
|
|
996
1061
|
}
|
|
997
|
-
const cloudignorePath =
|
|
998
|
-
if (await
|
|
999
|
-
const content = await
|
|
1062
|
+
const cloudignorePath = path3.join(dir, ".cloudignore");
|
|
1063
|
+
if (await fs3.pathExists(cloudignorePath)) {
|
|
1064
|
+
const content = await fs3.readFile(cloudignorePath, "utf-8");
|
|
1000
1065
|
ig.add(content);
|
|
1001
1066
|
}
|
|
1002
1067
|
const files = {};
|
|
1003
1068
|
const binaryFiles = {};
|
|
1004
1069
|
const skipped = [];
|
|
1005
1070
|
async function walk(currentDir) {
|
|
1006
|
-
const entries = await
|
|
1071
|
+
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
1007
1072
|
for (const entry of entries) {
|
|
1008
|
-
const fullPath =
|
|
1009
|
-
const relativePath =
|
|
1073
|
+
const fullPath = path3.join(currentDir, entry.name);
|
|
1074
|
+
const relativePath = path3.relative(dir, fullPath);
|
|
1010
1075
|
const ignorePath = entry.isDirectory() ? `${relativePath}/` : relativePath;
|
|
1011
1076
|
if (ig.ignores(ignorePath)) continue;
|
|
1012
1077
|
if (entry.isDirectory()) {
|
|
1013
1078
|
await walk(fullPath);
|
|
1014
1079
|
} else if (entry.isFile()) {
|
|
1015
|
-
const buffer = await
|
|
1080
|
+
const buffer = await fs3.readFile(fullPath);
|
|
1016
1081
|
if (buffer.byteLength > MAX_FILE_BYTES) {
|
|
1017
1082
|
skipped.push({
|
|
1018
1083
|
path: relativePath,
|
|
@@ -1118,6 +1183,16 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1118
1183
|
}
|
|
1119
1184
|
if (current.status === "running") {
|
|
1120
1185
|
spinner.succeed(chalk5.green(`Deployed successfully!`));
|
|
1186
|
+
try {
|
|
1187
|
+
const app = await client4.apps.get(appId);
|
|
1188
|
+
const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
|
|
1189
|
+
console.log(
|
|
1190
|
+
gated ? chalk5.gray(
|
|
1191
|
+
` Access: ${app.visibility || "org"} \u2014 visitors sign in once at the edge; your app receives X-User-Id / X-User-Email on every request. Don't build a login page.`
|
|
1192
|
+
) : chalk5.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
|
|
1193
|
+
);
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1121
1196
|
if (current.url) {
|
|
1122
1197
|
const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
|
|
1123
1198
|
console.log(chalk5.cyan(` URL: ${url}`));
|
|
@@ -1135,19 +1210,94 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1135
1210
|
}
|
|
1136
1211
|
});
|
|
1137
1212
|
|
|
1138
|
-
// src/commands/
|
|
1213
|
+
// src/commands/pull.ts
|
|
1139
1214
|
import { Command as Command5 } from "commander";
|
|
1140
1215
|
import chalk6 from "chalk";
|
|
1141
|
-
|
|
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) => {
|
|
1142
1292
|
const apiKey = getApiKey();
|
|
1143
1293
|
requireApiKey(apiKey);
|
|
1144
1294
|
const appId = opts.app || getCurrentApp();
|
|
1145
1295
|
if (!appId) {
|
|
1146
|
-
console.error(
|
|
1296
|
+
console.error(chalk7.red("No app specified. Use --app or link an app."));
|
|
1147
1297
|
process.exit(1);
|
|
1148
1298
|
}
|
|
1149
1299
|
if (opts.search && opts.pattern) {
|
|
1150
|
-
console.error(
|
|
1300
|
+
console.error(chalk7.red("Use either --search or --pattern, not both."));
|
|
1151
1301
|
process.exit(1);
|
|
1152
1302
|
}
|
|
1153
1303
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1170,43 +1320,43 @@ var logsCommand = new Command5("logs").description("View app logs").option("-a,
|
|
|
1170
1320
|
});
|
|
1171
1321
|
for (const entry of page.events) print(entry);
|
|
1172
1322
|
if (page.events.length === 0) {
|
|
1173
|
-
console.log(
|
|
1323
|
+
console.log(chalk7.gray(`No log lines since ${page.start}.`));
|
|
1174
1324
|
}
|
|
1175
1325
|
if (page.truncated) {
|
|
1176
1326
|
console.log(
|
|
1177
|
-
|
|
1327
|
+
chalk7.yellow(
|
|
1178
1328
|
"\nThis is part of the window -- there are more lines than one request reads. Narrow --since or add --search."
|
|
1179
1329
|
)
|
|
1180
1330
|
);
|
|
1181
1331
|
}
|
|
1182
1332
|
});
|
|
1183
1333
|
function print(entry) {
|
|
1184
|
-
console.log(`${
|
|
1334
|
+
console.log(`${chalk7.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
|
|
1185
1335
|
}
|
|
1186
1336
|
function formatLevel(level) {
|
|
1187
1337
|
switch (level) {
|
|
1188
1338
|
case "error":
|
|
1189
|
-
return
|
|
1339
|
+
return chalk7.red("[ERR]");
|
|
1190
1340
|
case "warn":
|
|
1191
|
-
return
|
|
1341
|
+
return chalk7.yellow("[WRN]");
|
|
1192
1342
|
case "info":
|
|
1193
|
-
return
|
|
1343
|
+
return chalk7.blue("[INF]");
|
|
1194
1344
|
case "debug":
|
|
1195
|
-
return
|
|
1345
|
+
return chalk7.gray("[DBG]");
|
|
1196
1346
|
default:
|
|
1197
|
-
return
|
|
1347
|
+
return chalk7.gray("[LOG]");
|
|
1198
1348
|
}
|
|
1199
1349
|
}
|
|
1200
1350
|
|
|
1201
1351
|
// src/commands/metrics.ts
|
|
1202
|
-
import { Command as
|
|
1203
|
-
import
|
|
1204
|
-
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) => {
|
|
1205
1355
|
const apiKey = getApiKey();
|
|
1206
1356
|
requireApiKey(apiKey);
|
|
1207
1357
|
const appId = opts.app || getCurrentApp();
|
|
1208
1358
|
if (!appId) {
|
|
1209
|
-
console.error(
|
|
1359
|
+
console.error(chalk8.red("No app specified. Use --app or link an app."));
|
|
1210
1360
|
process.exit(1);
|
|
1211
1361
|
}
|
|
1212
1362
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1217,7 +1367,7 @@ var metricsCommand = new Command6("metrics").description("Traffic, latency and r
|
|
|
1217
1367
|
}
|
|
1218
1368
|
const t = metrics.totals;
|
|
1219
1369
|
console.log(
|
|
1220
|
-
|
|
1370
|
+
chalk8.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
|
|
1221
1371
|
);
|
|
1222
1372
|
console.log();
|
|
1223
1373
|
console.log(` ${label("Requests")} ${t.requests}`);
|
|
@@ -1225,76 +1375,76 @@ var metricsCommand = new Command6("metrics").description("Traffic, latency and r
|
|
|
1225
1375
|
console.log(` ${label("5xx")} ${count(t.serverErrors, true)}`);
|
|
1226
1376
|
console.log(` ${label("Gate denied")} ${count(t.gateDenied)}`);
|
|
1227
1377
|
console.log(` ${label("Latency avg")} ${ms(t.latencyAvgMs)}`);
|
|
1228
|
-
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${
|
|
1378
|
+
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk8.dim("(sampled)")}`);
|
|
1229
1379
|
console.log(` ${label("CPU avg")} ${units(t.cpuAvg)}`);
|
|
1230
1380
|
console.log(` ${label("Memory avg")} ${mb(t.memoryAvgMb)}`);
|
|
1231
1381
|
if (t.requests === 0) {
|
|
1232
1382
|
console.log();
|
|
1233
|
-
console.log(
|
|
1383
|
+
console.log(chalk8.dim("No requests recorded in this window."));
|
|
1234
1384
|
}
|
|
1235
1385
|
if (opts.series) {
|
|
1236
1386
|
console.log();
|
|
1237
1387
|
for (const [name, points] of Object.entries(metrics.series)) {
|
|
1238
1388
|
if (points.length === 0) continue;
|
|
1239
|
-
console.log(
|
|
1240
|
-
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}`);
|
|
1241
1391
|
}
|
|
1242
1392
|
}
|
|
1243
1393
|
});
|
|
1244
1394
|
function label(text) {
|
|
1245
|
-
return
|
|
1395
|
+
return chalk8.dim(text.padEnd(13));
|
|
1246
1396
|
}
|
|
1247
1397
|
function count(n, severe = false) {
|
|
1248
|
-
if (n === 0) return
|
|
1249
|
-
return severe ?
|
|
1398
|
+
if (n === 0) return chalk8.dim("0");
|
|
1399
|
+
return severe ? chalk8.red(String(n)) : chalk8.yellow(String(n));
|
|
1250
1400
|
}
|
|
1251
1401
|
function ms(v) {
|
|
1252
|
-
return v === null ?
|
|
1402
|
+
return v === null ? chalk8.dim("\u2014") : `${v} ms`;
|
|
1253
1403
|
}
|
|
1254
1404
|
function mb(v) {
|
|
1255
|
-
return v === null ?
|
|
1405
|
+
return v === null ? chalk8.dim("\u2014") : `${v} MB`;
|
|
1256
1406
|
}
|
|
1257
1407
|
function units(v) {
|
|
1258
|
-
return v === null ?
|
|
1408
|
+
return v === null ? chalk8.dim("\u2014") : `${v} CPU units`;
|
|
1259
1409
|
}
|
|
1260
1410
|
|
|
1261
1411
|
// src/commands/rollback.ts
|
|
1262
|
-
import { Command as
|
|
1263
|
-
import
|
|
1264
|
-
import
|
|
1265
|
-
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) => {
|
|
1266
1416
|
const apiKey = getApiKey();
|
|
1267
1417
|
requireApiKey(apiKey);
|
|
1268
1418
|
const appId = opts.app || getCurrentApp();
|
|
1269
1419
|
if (!appId) {
|
|
1270
|
-
console.error(
|
|
1420
|
+
console.error(chalk9.red("No app specified. Use --app or link an app."));
|
|
1271
1421
|
process.exit(1);
|
|
1272
1422
|
}
|
|
1273
1423
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1274
|
-
const spinner =
|
|
1424
|
+
const spinner = ora4("Rolling back...").start();
|
|
1275
1425
|
try {
|
|
1276
1426
|
const deployment = await client4.apps.rollback(appId);
|
|
1277
|
-
spinner.succeed(
|
|
1427
|
+
spinner.succeed(chalk9.green("Rollback successful!"));
|
|
1278
1428
|
if (deployment.url) {
|
|
1279
|
-
console.log(
|
|
1429
|
+
console.log(chalk9.cyan(` URL: ${deployment.url}`));
|
|
1280
1430
|
}
|
|
1281
1431
|
} catch (error) {
|
|
1282
|
-
spinner.fail(
|
|
1432
|
+
spinner.fail(chalk9.red(`Rollback failed: ${error.message}`));
|
|
1283
1433
|
process.exit(1);
|
|
1284
1434
|
}
|
|
1285
1435
|
});
|
|
1286
1436
|
|
|
1287
1437
|
// src/commands/secrets.ts
|
|
1288
|
-
import { Command as
|
|
1289
|
-
import
|
|
1438
|
+
import { Command as Command9 } from "commander";
|
|
1439
|
+
import chalk10 from "chalk";
|
|
1290
1440
|
import inquirer3 from "inquirer";
|
|
1291
|
-
var secretsCommand = new
|
|
1441
|
+
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1292
1442
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1293
1443
|
const apiKey = getApiKey();
|
|
1294
1444
|
requireApiKey(apiKey);
|
|
1295
1445
|
const appId = opts.app || getCurrentApp();
|
|
1296
1446
|
if (!appId) {
|
|
1297
|
-
console.error(
|
|
1447
|
+
console.error(chalk10.red("No app specified."));
|
|
1298
1448
|
process.exit(1);
|
|
1299
1449
|
}
|
|
1300
1450
|
if (!value) {
|
|
@@ -1305,20 +1455,20 @@ secretsCommand.command("set <key> [value]").description("Set a secret").option("
|
|
|
1305
1455
|
}
|
|
1306
1456
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1307
1457
|
await client4.secrets.set(appId, key, value);
|
|
1308
|
-
console.log(
|
|
1458
|
+
console.log(chalk10.green(`Secret "${key}" set successfully.`));
|
|
1309
1459
|
});
|
|
1310
1460
|
secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1311
1461
|
const apiKey = getApiKey();
|
|
1312
1462
|
requireApiKey(apiKey);
|
|
1313
1463
|
const appId = opts.app || getCurrentApp();
|
|
1314
1464
|
if (!appId) {
|
|
1315
|
-
console.error(
|
|
1465
|
+
console.error(chalk10.red("No app specified."));
|
|
1316
1466
|
process.exit(1);
|
|
1317
1467
|
}
|
|
1318
1468
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1319
1469
|
const secrets = await client4.secrets.list(appId);
|
|
1320
1470
|
if (secrets.length === 0) {
|
|
1321
|
-
console.log(
|
|
1471
|
+
console.log(chalk10.gray("No secrets configured."));
|
|
1322
1472
|
return;
|
|
1323
1473
|
}
|
|
1324
1474
|
console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
|
|
@@ -1331,24 +1481,24 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
|
|
|
1331
1481
|
requireApiKey(apiKey);
|
|
1332
1482
|
const appId = opts.app || getCurrentApp();
|
|
1333
1483
|
if (!appId) {
|
|
1334
|
-
console.error(
|
|
1484
|
+
console.error(chalk10.red("No app specified."));
|
|
1335
1485
|
process.exit(1);
|
|
1336
1486
|
}
|
|
1337
1487
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1338
1488
|
await client4.secrets.delete(appId, key);
|
|
1339
|
-
console.log(
|
|
1489
|
+
console.log(chalk10.green(`Secret "${key}" removed.`));
|
|
1340
1490
|
});
|
|
1341
1491
|
|
|
1342
1492
|
// src/commands/apps.ts
|
|
1343
|
-
import { Command as
|
|
1344
|
-
import
|
|
1493
|
+
import { Command as Command10 } from "commander";
|
|
1494
|
+
import chalk11 from "chalk";
|
|
1345
1495
|
import inquirer4 from "inquirer";
|
|
1346
|
-
var appsCommand = new
|
|
1496
|
+
var appsCommand = new Command10("apps").description("Manage apps");
|
|
1347
1497
|
function accessOption(value) {
|
|
1348
1498
|
if (value === void 0) return void 0;
|
|
1349
1499
|
const tier = value.trim().toLowerCase();
|
|
1350
1500
|
if (!isVisibility(tier)) {
|
|
1351
|
-
console.error(
|
|
1501
|
+
console.error(chalk11.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
|
|
1352
1502
|
process.exit(1);
|
|
1353
1503
|
}
|
|
1354
1504
|
return tier;
|
|
@@ -1364,7 +1514,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1364
1514
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1365
1515
|
const apps = await client4.apps.list(orgId);
|
|
1366
1516
|
if (apps.length === 0) {
|
|
1367
|
-
console.log(
|
|
1517
|
+
console.log(chalk11.gray("No apps yet. Create one with `videan apps create`."));
|
|
1368
1518
|
return;
|
|
1369
1519
|
}
|
|
1370
1520
|
const widths = [20, 24, 10, 44];
|
|
@@ -1386,7 +1536,7 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1386
1536
|
name = answers.name;
|
|
1387
1537
|
}
|
|
1388
1538
|
if (!name) {
|
|
1389
|
-
console.error(
|
|
1539
|
+
console.error(chalk11.red("An app name is required."));
|
|
1390
1540
|
process.exit(1);
|
|
1391
1541
|
}
|
|
1392
1542
|
const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
|
|
@@ -1400,10 +1550,10 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1400
1550
|
visibility
|
|
1401
1551
|
});
|
|
1402
1552
|
setCurrentApp(app.id);
|
|
1403
|
-
console.log(
|
|
1404
|
-
console.log(
|
|
1405
|
-
console.log(
|
|
1406
|
-
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."));
|
|
1407
1557
|
}
|
|
1408
1558
|
);
|
|
1409
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(
|
|
@@ -1412,16 +1562,16 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1412
1562
|
requireApiKey(apiKey);
|
|
1413
1563
|
const appId = opts.app || getCurrentApp();
|
|
1414
1564
|
if (!appId) {
|
|
1415
|
-
console.error(
|
|
1565
|
+
console.error(chalk11.red("No app specified. Use --app or link an app."));
|
|
1416
1566
|
process.exit(1);
|
|
1417
1567
|
}
|
|
1418
1568
|
if (opts.public && opts.private) {
|
|
1419
|
-
console.error(
|
|
1569
|
+
console.error(chalk11.red("--public and --private contradict each other."));
|
|
1420
1570
|
process.exit(1);
|
|
1421
1571
|
}
|
|
1422
1572
|
const access = accessOption(opts.access);
|
|
1423
1573
|
if (access && (opts.public || opts.private)) {
|
|
1424
|
-
console.error(
|
|
1574
|
+
console.error(chalk11.red("--access already says who may open the app; drop --public/--private."));
|
|
1425
1575
|
process.exit(1);
|
|
1426
1576
|
}
|
|
1427
1577
|
const changes = {};
|
|
@@ -1442,7 +1592,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1442
1592
|
if (opts.storage) changes.storage = true;
|
|
1443
1593
|
if (opts.email) changes.email = true;
|
|
1444
1594
|
if (Object.keys(changes).length === 0) {
|
|
1445
|
-
console.error(
|
|
1595
|
+
console.error(chalk11.red("Nothing to change. Pass at least one option."));
|
|
1446
1596
|
process.exit(1);
|
|
1447
1597
|
}
|
|
1448
1598
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1451,7 +1601,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1451
1601
|
console.log(JSON.stringify(app, null, 2));
|
|
1452
1602
|
return;
|
|
1453
1603
|
}
|
|
1454
|
-
console.log(
|
|
1604
|
+
console.log(chalk11.green(`Updated ${app.name} (${app.id})`));
|
|
1455
1605
|
console.log(` Access: ${describeAccess(app)}`);
|
|
1456
1606
|
console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
|
|
1457
1607
|
console.log(
|
|
@@ -1459,7 +1609,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1459
1609
|
);
|
|
1460
1610
|
if (app.restartRequired) {
|
|
1461
1611
|
console.log(
|
|
1462
|
-
|
|
1612
|
+
chalk11.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
|
|
1463
1613
|
);
|
|
1464
1614
|
}
|
|
1465
1615
|
}
|
|
@@ -1467,7 +1617,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1467
1617
|
function intOption(flag, raw) {
|
|
1468
1618
|
const n = Number(raw);
|
|
1469
1619
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1470
|
-
console.error(
|
|
1620
|
+
console.error(chalk11.red(`${flag} expects a positive whole number, got "${raw}".`));
|
|
1471
1621
|
process.exit(1);
|
|
1472
1622
|
}
|
|
1473
1623
|
return n;
|
|
@@ -1486,64 +1636,64 @@ appsCommand.command("delete <id>").description("Delete an app and all its resour
|
|
|
1486
1636
|
if (!confirm) return;
|
|
1487
1637
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1488
1638
|
const { warnings } = await client4.apps.delete(id);
|
|
1489
|
-
console.log(
|
|
1639
|
+
console.log(chalk11.green("App deleted."));
|
|
1490
1640
|
if (warnings.length > 0) {
|
|
1491
|
-
console.log(
|
|
1641
|
+
console.log(chalk11.yellow("\nSome resources could not be released:"));
|
|
1492
1642
|
for (const w of warnings) {
|
|
1493
|
-
console.log(
|
|
1643
|
+
console.log(chalk11.yellow(` ${w.step}: ${w.error || "unknown error"}`));
|
|
1494
1644
|
}
|
|
1495
1645
|
}
|
|
1496
1646
|
});
|
|
1497
1647
|
|
|
1498
1648
|
// src/commands/status.ts
|
|
1499
|
-
import { Command as
|
|
1500
|
-
import
|
|
1649
|
+
import { Command as Command11 } from "commander";
|
|
1650
|
+
import chalk12 from "chalk";
|
|
1501
1651
|
function accessLine(app) {
|
|
1502
1652
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1503
1653
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
1504
1654
|
}
|
|
1505
|
-
var statusCommand = new
|
|
1655
|
+
var statusCommand = new Command11("status").description("Show current app status").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1506
1656
|
const apiKey = getApiKey();
|
|
1507
1657
|
requireApiKey(apiKey);
|
|
1508
1658
|
const appId = opts.app || getCurrentApp();
|
|
1509
1659
|
if (!appId) {
|
|
1510
|
-
console.error(
|
|
1660
|
+
console.error(chalk12.red("No app specified. Use --app or link an app."));
|
|
1511
1661
|
process.exit(1);
|
|
1512
1662
|
}
|
|
1513
1663
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1514
1664
|
const app = await client4.apps.get(appId);
|
|
1515
1665
|
const health = await client4.apps.health(appId);
|
|
1516
|
-
console.log(
|
|
1666
|
+
console.log(chalk12.bold(app.name));
|
|
1517
1667
|
console.log(` ID: ${app.id}`);
|
|
1518
1668
|
console.log(` Created: ${app.createdAt}`);
|
|
1519
1669
|
console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
|
|
1520
1670
|
console.log(` Access: ${accessLine(app)}`);
|
|
1521
1671
|
console.log(` State: ${formatStatus(health.state)}`);
|
|
1522
|
-
console.log(` Healthy: ${health.healthy ?
|
|
1523
|
-
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))}`);
|
|
1524
1674
|
const task = health.task;
|
|
1525
1675
|
if (task) {
|
|
1526
1676
|
if (task.exit_code !== void 0 && task.exit_code !== null) {
|
|
1527
1677
|
const code = String(task.exit_code);
|
|
1528
|
-
console.log(` Exit code: ${task.exit_code === 0 ? code :
|
|
1678
|
+
console.log(` Exit code: ${task.exit_code === 0 ? code : chalk12.red(code)}`);
|
|
1529
1679
|
}
|
|
1530
1680
|
if (task.stopped_reason) {
|
|
1531
|
-
console.log(` Stopped: ${
|
|
1681
|
+
console.log(` Stopped: ${chalk12.yellow(task.stopped_reason)}`);
|
|
1532
1682
|
}
|
|
1533
1683
|
if (task.container_reason) {
|
|
1534
|
-
console.log(` Detail: ${
|
|
1684
|
+
console.log(` Detail: ${chalk12.yellow(task.container_reason)}`);
|
|
1535
1685
|
}
|
|
1536
1686
|
}
|
|
1537
1687
|
if (!health.healthy) {
|
|
1538
|
-
console.log(
|
|
1688
|
+
console.log(chalk12.gray("\nRun `videan logs` to see why."));
|
|
1539
1689
|
}
|
|
1540
1690
|
});
|
|
1541
1691
|
|
|
1542
1692
|
// src/commands/keys.ts
|
|
1543
|
-
import { Command as
|
|
1544
|
-
import
|
|
1693
|
+
import { Command as Command12 } from "commander";
|
|
1694
|
+
import chalk13 from "chalk";
|
|
1545
1695
|
import inquirer5 from "inquirer";
|
|
1546
|
-
var keysCommand = new
|
|
1696
|
+
var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
|
|
1547
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(
|
|
1548
1698
|
"--app <id>",
|
|
1549
1699
|
"Pin the key to one app (a service token, for code running inside it)"
|
|
@@ -1555,7 +1705,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1555
1705
|
if (opts.expires !== void 0) {
|
|
1556
1706
|
expiresInDays = Number(opts.expires);
|
|
1557
1707
|
if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
|
|
1558
|
-
console.error(
|
|
1708
|
+
console.error(chalk13.red("--expires must be a whole number of days, at least 1."));
|
|
1559
1709
|
process.exit(1);
|
|
1560
1710
|
}
|
|
1561
1711
|
}
|
|
@@ -1566,22 +1716,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1566
1716
|
expiresInDays,
|
|
1567
1717
|
appId: opts.app
|
|
1568
1718
|
});
|
|
1569
|
-
console.log(
|
|
1719
|
+
console.log(chalk13.green(`Created key "${key.name}" (${key.id})`));
|
|
1570
1720
|
console.log(`
|
|
1571
|
-
${
|
|
1721
|
+
${chalk13.bold(key.key)}
|
|
1572
1722
|
`);
|
|
1573
|
-
console.log(
|
|
1723
|
+
console.log(chalk13.yellow("Copy it now. It cannot be shown again."));
|
|
1574
1724
|
if (key.appId) {
|
|
1575
1725
|
console.log(
|
|
1576
|
-
|
|
1726
|
+
chalk13.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
|
|
1577
1727
|
);
|
|
1578
1728
|
} else {
|
|
1579
1729
|
console.log(
|
|
1580
|
-
|
|
1730
|
+
chalk13.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
|
|
1581
1731
|
);
|
|
1582
1732
|
}
|
|
1583
1733
|
if (key.expiresAt) {
|
|
1584
|
-
console.log(
|
|
1734
|
+
console.log(chalk13.gray(`Expires ${key.expiresAt}`));
|
|
1585
1735
|
}
|
|
1586
1736
|
});
|
|
1587
1737
|
keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
@@ -1591,13 +1741,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1591
1741
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1592
1742
|
const keys = await client4.apiKeys.list(orgId);
|
|
1593
1743
|
if (keys.length === 0) {
|
|
1594
|
-
console.log(
|
|
1744
|
+
console.log(chalk13.gray("No API keys. Create one with `videan keys create <name>`."));
|
|
1595
1745
|
return;
|
|
1596
1746
|
}
|
|
1597
1747
|
const widths = [24, 24, 16, 22, 12, 24];
|
|
1598
1748
|
console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
|
|
1599
1749
|
for (const key of keys) {
|
|
1600
|
-
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");
|
|
1601
1751
|
console.log(formatRow(
|
|
1602
1752
|
[
|
|
1603
1753
|
key.name,
|
|
@@ -1620,13 +1770,13 @@ keysCommand.command("revoke <id>").description("Revoke an API key").action(async
|
|
|
1620
1770
|
if (!confirm) return;
|
|
1621
1771
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1622
1772
|
await client4.apiKeys.revoke(id);
|
|
1623
|
-
console.log(
|
|
1773
|
+
console.log(chalk13.green("Key revoked."));
|
|
1624
1774
|
});
|
|
1625
1775
|
|
|
1626
1776
|
// src/commands/orgs.ts
|
|
1627
|
-
import { Command as
|
|
1628
|
-
import
|
|
1629
|
-
var orgsCommand = new
|
|
1777
|
+
import { Command as Command13 } from "commander";
|
|
1778
|
+
import chalk14 from "chalk";
|
|
1779
|
+
var orgsCommand = new Command13("orgs").description("Manage organizations");
|
|
1630
1780
|
orgsCommand.command("list").description("List your organizations").action(async () => {
|
|
1631
1781
|
const apiKey = getApiKey();
|
|
1632
1782
|
requireApiKey(apiKey);
|
|
@@ -1634,14 +1784,14 @@ orgsCommand.command("list").description("List your organizations").action(async
|
|
|
1634
1784
|
const orgs = await client4.orgs.list();
|
|
1635
1785
|
const current = getDefaultOrg();
|
|
1636
1786
|
if (orgs.length === 0) {
|
|
1637
|
-
console.log(
|
|
1787
|
+
console.log(chalk14.gray("No organizations. Create one with `videan orgs create <name>`."));
|
|
1638
1788
|
return;
|
|
1639
1789
|
}
|
|
1640
1790
|
const widths = [24, 24, 10, 8];
|
|
1641
1791
|
console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
|
|
1642
1792
|
for (const org of orgs) {
|
|
1643
1793
|
console.log(formatRow(
|
|
1644
|
-
[org.name, org.id, org.plan, org.id === current ?
|
|
1794
|
+
[org.name, org.id, org.plan, org.id === current ? chalk14.green("default") : ""],
|
|
1645
1795
|
widths
|
|
1646
1796
|
));
|
|
1647
1797
|
}
|
|
@@ -1652,7 +1802,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
|
|
|
1652
1802
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1653
1803
|
const org = await client4.orgs.create(name);
|
|
1654
1804
|
if (!getDefaultOrg()) setDefaultOrg(org.id);
|
|
1655
|
-
console.log(
|
|
1805
|
+
console.log(chalk14.green(`Organization "${org.name}" created (${org.id})`));
|
|
1656
1806
|
});
|
|
1657
1807
|
orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
|
|
1658
1808
|
const apiKey = getApiKey();
|
|
@@ -1660,7 +1810,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
|
|
|
1660
1810
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1661
1811
|
const org = await client4.orgs.get(id);
|
|
1662
1812
|
setDefaultOrg(org.id);
|
|
1663
|
-
console.log(
|
|
1813
|
+
console.log(chalk14.green(`Default organization set to "${org.name}".`));
|
|
1664
1814
|
});
|
|
1665
1815
|
orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
1666
1816
|
const apiKey = getApiKey();
|
|
@@ -1683,15 +1833,15 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1683
1833
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1684
1834
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1685
1835
|
await client4.orgs.invite(orgId, email);
|
|
1686
|
-
console.log(
|
|
1836
|
+
console.log(chalk14.green(`Invited ${email}.`));
|
|
1687
1837
|
});
|
|
1688
1838
|
|
|
1689
1839
|
// src/commands/storage.ts
|
|
1690
|
-
import { Command as
|
|
1691
|
-
import
|
|
1692
|
-
import
|
|
1693
|
-
import
|
|
1694
|
-
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");
|
|
1695
1845
|
function client() {
|
|
1696
1846
|
const apiKey = getApiKey();
|
|
1697
1847
|
requireApiKey(apiKey);
|
|
@@ -1700,7 +1850,7 @@ function client() {
|
|
|
1700
1850
|
function requireApp(opt) {
|
|
1701
1851
|
const appId = opt || getCurrentApp();
|
|
1702
1852
|
if (!appId) {
|
|
1703
|
-
console.error(
|
|
1853
|
+
console.error(chalk15.red("No app specified. Use --app or link an app."));
|
|
1704
1854
|
process.exit(1);
|
|
1705
1855
|
}
|
|
1706
1856
|
return appId;
|
|
@@ -1723,7 +1873,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1723
1873
|
return;
|
|
1724
1874
|
}
|
|
1725
1875
|
if (objects.length === 0) {
|
|
1726
|
-
console.log(
|
|
1876
|
+
console.log(chalk15.gray("No objects stored. Upload one with `videan storage put <file>`."));
|
|
1727
1877
|
return;
|
|
1728
1878
|
}
|
|
1729
1879
|
const widths = [52, 12, 24];
|
|
@@ -1732,42 +1882,42 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1732
1882
|
console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
|
|
1733
1883
|
}
|
|
1734
1884
|
const total = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1735
|
-
console.log(
|
|
1885
|
+
console.log(chalk15.dim(`
|
|
1736
1886
|
${objects.length} object(s), ${human(total)}`));
|
|
1737
1887
|
});
|
|
1738
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) => {
|
|
1739
1889
|
const appId = requireApp(opts.app);
|
|
1740
|
-
if (!await
|
|
1741
|
-
console.error(
|
|
1890
|
+
if (!await fs5.pathExists(file)) {
|
|
1891
|
+
console.error(chalk15.red(`No such file: ${file}`));
|
|
1742
1892
|
process.exit(1);
|
|
1743
1893
|
}
|
|
1744
|
-
const body = await
|
|
1745
|
-
const objectKey = key ||
|
|
1894
|
+
const body = await fs5.readFile(file);
|
|
1895
|
+
const objectKey = key || path5.basename(file);
|
|
1746
1896
|
const stored = await client().storage.upload(appId, objectKey, body, {
|
|
1747
1897
|
contentType: opts.type
|
|
1748
1898
|
});
|
|
1749
1899
|
console.log(
|
|
1750
|
-
|
|
1900
|
+
chalk15.green(`Uploaded ${objectKey}`) + chalk15.dim(` (${human(body.length)})`)
|
|
1751
1901
|
);
|
|
1752
|
-
if (stored.etag) console.log(
|
|
1902
|
+
if (stored.etag) console.log(chalk15.dim(`etag ${stored.etag}`));
|
|
1753
1903
|
});
|
|
1754
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) => {
|
|
1755
1905
|
const appId = requireApp(opts.app);
|
|
1756
1906
|
const bytes = await client().storage.download(appId, key);
|
|
1757
|
-
const dest = file ||
|
|
1758
|
-
await
|
|
1759
|
-
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)})`));
|
|
1760
1910
|
});
|
|
1761
1911
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
1762
1912
|
const appId = requireApp(opts.app);
|
|
1763
1913
|
await client().storage.delete(appId, key);
|
|
1764
|
-
console.log(
|
|
1914
|
+
console.log(chalk15.green(`Deleted ${key}`));
|
|
1765
1915
|
});
|
|
1766
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) => {
|
|
1767
1917
|
const appId = requireApp(opts.app);
|
|
1768
1918
|
const expiresIn = Number(opts.expires);
|
|
1769
1919
|
if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
|
|
1770
|
-
console.error(
|
|
1920
|
+
console.error(chalk15.red("--expires must be a whole number of seconds between 30 and 3600."));
|
|
1771
1921
|
process.exit(1);
|
|
1772
1922
|
}
|
|
1773
1923
|
const signed = await client().storage.presign(appId, key, {
|
|
@@ -1777,7 +1927,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
|
|
|
1777
1927
|
});
|
|
1778
1928
|
console.log(signed.url);
|
|
1779
1929
|
console.error(
|
|
1780
|
-
|
|
1930
|
+
chalk15.yellow(
|
|
1781
1931
|
`This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
|
|
1782
1932
|
)
|
|
1783
1933
|
);
|
|
@@ -1789,10 +1939,10 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1789
1939
|
});
|
|
1790
1940
|
|
|
1791
1941
|
// src/commands/email.ts
|
|
1792
|
-
import { Command as
|
|
1793
|
-
import
|
|
1794
|
-
import
|
|
1795
|
-
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");
|
|
1796
1946
|
function client2() {
|
|
1797
1947
|
const apiKey = getApiKey();
|
|
1798
1948
|
requireApiKey(apiKey);
|
|
@@ -1801,7 +1951,7 @@ function client2() {
|
|
|
1801
1951
|
function requireApp2(opt) {
|
|
1802
1952
|
const appId = opt || getCurrentApp();
|
|
1803
1953
|
if (!appId) {
|
|
1804
|
-
console.error(
|
|
1954
|
+
console.error(chalk16.red("No app specified. Use --app or link an app."));
|
|
1805
1955
|
process.exit(1);
|
|
1806
1956
|
}
|
|
1807
1957
|
return appId;
|
|
@@ -1812,12 +1962,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1812
1962
|
let text = opts.text;
|
|
1813
1963
|
let html = opts.html;
|
|
1814
1964
|
if (opts.bodyFile) {
|
|
1815
|
-
const contents = await
|
|
1965
|
+
const contents = await fs6.readFile(opts.bodyFile, "utf-8");
|
|
1816
1966
|
if (/\.html?$/i.test(opts.bodyFile)) html = contents;
|
|
1817
1967
|
else text = contents;
|
|
1818
1968
|
}
|
|
1819
1969
|
if (!text && !html) {
|
|
1820
|
-
console.error(
|
|
1970
|
+
console.error(chalk16.red("Nothing to send. Pass --text, --html or --body-file."));
|
|
1821
1971
|
process.exit(1);
|
|
1822
1972
|
}
|
|
1823
1973
|
const sent = await client2().email.send(appId, {
|
|
@@ -1831,12 +1981,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1831
1981
|
console.log(JSON.stringify(sent, null, 2));
|
|
1832
1982
|
return;
|
|
1833
1983
|
}
|
|
1834
|
-
console.log(
|
|
1984
|
+
console.log(chalk16.green("Sent"));
|
|
1835
1985
|
console.log(` From: ${sent.from}`);
|
|
1836
1986
|
console.log(` To: ${sent.to.join(", ")}`);
|
|
1837
1987
|
console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
|
|
1838
1988
|
console.log(
|
|
1839
|
-
|
|
1989
|
+
chalk16.dim(
|
|
1840
1990
|
` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
|
|
1841
1991
|
)
|
|
1842
1992
|
);
|
|
@@ -1851,14 +2001,14 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
1851
2001
|
console.log(`Sent today: ${quota.sent}`);
|
|
1852
2002
|
console.log(`Daily quota: ${quota.quota}`);
|
|
1853
2003
|
console.log(`Remaining: ${quota.remaining}`);
|
|
1854
|
-
console.log(
|
|
2004
|
+
console.log(chalk16.dim(`Resets at: ${quota.resetsAt}`));
|
|
1855
2005
|
});
|
|
1856
2006
|
|
|
1857
2007
|
// src/commands/jobs.ts
|
|
1858
|
-
import { Command as
|
|
1859
|
-
import
|
|
1860
|
-
import
|
|
1861
|
-
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");
|
|
1862
2012
|
function client3() {
|
|
1863
2013
|
const apiKey = getApiKey();
|
|
1864
2014
|
requireApiKey(apiKey);
|
|
@@ -1867,7 +2017,7 @@ function client3() {
|
|
|
1867
2017
|
function requireApp3(opt) {
|
|
1868
2018
|
const appId = opt || getCurrentApp();
|
|
1869
2019
|
if (!appId) {
|
|
1870
|
-
console.error(
|
|
2020
|
+
console.error(chalk17.red("No app specified. Use --app or run `videan init` first."));
|
|
1871
2021
|
process.exit(1);
|
|
1872
2022
|
}
|
|
1873
2023
|
return appId;
|
|
@@ -1875,30 +2025,30 @@ function requireApp3(opt) {
|
|
|
1875
2025
|
function intOption2(flag, raw) {
|
|
1876
2026
|
const n = Number(raw);
|
|
1877
2027
|
if (!Number.isInteger(n) || n < 0) {
|
|
1878
|
-
console.error(
|
|
2028
|
+
console.error(chalk17.red(`${flag} expects a whole number, got "${raw}".`));
|
|
1879
2029
|
process.exit(1);
|
|
1880
2030
|
}
|
|
1881
2031
|
return n;
|
|
1882
2032
|
}
|
|
1883
2033
|
var STATUS_COLOR = {
|
|
1884
|
-
queued:
|
|
1885
|
-
running:
|
|
1886
|
-
succeeded:
|
|
1887
|
-
dead:
|
|
1888
|
-
cancelled:
|
|
2034
|
+
queued: chalk17.yellow,
|
|
2035
|
+
running: chalk17.cyan,
|
|
2036
|
+
succeeded: chalk17.green,
|
|
2037
|
+
dead: chalk17.red,
|
|
2038
|
+
cancelled: chalk17.gray
|
|
1889
2039
|
};
|
|
1890
2040
|
function statusLabel(status) {
|
|
1891
|
-
return (STATUS_COLOR[status] ??
|
|
2041
|
+
return (STATUS_COLOR[status] ?? chalk17.white)(status);
|
|
1892
2042
|
}
|
|
1893
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) => {
|
|
1894
2044
|
const appId = requireApp3(opts.app);
|
|
1895
2045
|
let payload;
|
|
1896
|
-
const raw = opts.file ? await
|
|
2046
|
+
const raw = opts.file ? await fs7.readFile(opts.file, "utf-8") : opts.data;
|
|
1897
2047
|
if (raw !== void 0) {
|
|
1898
2048
|
try {
|
|
1899
2049
|
payload = JSON.parse(raw);
|
|
1900
2050
|
} catch (e) {
|
|
1901
|
-
console.error(
|
|
2051
|
+
console.error(chalk17.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
|
|
1902
2052
|
process.exit(1);
|
|
1903
2053
|
}
|
|
1904
2054
|
}
|
|
@@ -1916,13 +2066,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
1916
2066
|
return;
|
|
1917
2067
|
}
|
|
1918
2068
|
if (job.deduplicated) {
|
|
1919
|
-
console.log(
|
|
2069
|
+
console.log(chalk17.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
|
|
1920
2070
|
} else {
|
|
1921
|
-
console.log(
|
|
2071
|
+
console.log(chalk17.green(`Queued ${job.id}`));
|
|
1922
2072
|
}
|
|
1923
|
-
console.log(
|
|
2073
|
+
console.log(chalk17.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
1924
2074
|
if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
|
|
1925
|
-
console.log(
|
|
2075
|
+
console.log(chalk17.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
|
|
1926
2076
|
}
|
|
1927
2077
|
});
|
|
1928
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) => {
|
|
@@ -1937,17 +2087,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
|
|
|
1937
2087
|
return;
|
|
1938
2088
|
}
|
|
1939
2089
|
const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
|
|
1940
|
-
console.log(depth ||
|
|
2090
|
+
console.log(depth || chalk17.gray("No jobs yet."));
|
|
1941
2091
|
if (jobs.length === 0) return;
|
|
1942
2092
|
console.log();
|
|
1943
2093
|
for (const job of jobs) {
|
|
1944
|
-
const attempts = job.attempts > 0 ?
|
|
2094
|
+
const attempts = job.attempts > 0 ? chalk17.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
|
|
1945
2095
|
console.log(
|
|
1946
|
-
`${statusLabel(job.status).padEnd(20)} ${
|
|
2096
|
+
`${statusLabel(job.status).padEnd(20)} ${chalk17.white(job.id)} ${chalk17.gray(
|
|
1947
2097
|
`${job.queue} ${job.path}`
|
|
1948
2098
|
)}${attempts}`
|
|
1949
2099
|
);
|
|
1950
|
-
if (job.error) console.log(
|
|
2100
|
+
if (job.error) console.log(chalk17.red(` ${job.error}`));
|
|
1951
2101
|
}
|
|
1952
2102
|
});
|
|
1953
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) => {
|
|
@@ -1956,17 +2106,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
1956
2106
|
console.log(JSON.stringify(job, null, 2));
|
|
1957
2107
|
return;
|
|
1958
2108
|
}
|
|
1959
|
-
console.log(`${
|
|
1960
|
-
console.log(
|
|
1961
|
-
console.log(
|
|
1962
|
-
if (job.statusCode) console.log(
|
|
1963
|
-
if (job.durationMs) console.log(
|
|
1964
|
-
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}`));
|
|
1965
2115
|
if (job.status === "queued" && job.attempts > 0) {
|
|
1966
|
-
console.log(
|
|
2116
|
+
console.log(chalk17.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
|
|
1967
2117
|
}
|
|
1968
2118
|
if (job.payload !== null && job.payload !== void 0) {
|
|
1969
|
-
console.log(
|
|
2119
|
+
console.log(chalk17.gray(" Payload:"));
|
|
1970
2120
|
console.log(
|
|
1971
2121
|
JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
|
|
1972
2122
|
);
|
|
@@ -1974,17 +2124,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
1974
2124
|
});
|
|
1975
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) => {
|
|
1976
2126
|
const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
|
|
1977
|
-
console.log(
|
|
2127
|
+
console.log(chalk17.green(`Requeued ${job.id}`));
|
|
1978
2128
|
});
|
|
1979
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) => {
|
|
1980
2130
|
const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
|
|
1981
|
-
console.log(
|
|
2131
|
+
console.log(chalk17.green(`Cancelled ${job.id}`));
|
|
1982
2132
|
});
|
|
1983
2133
|
|
|
1984
2134
|
// src/commands/usage.ts
|
|
1985
|
-
import { Command as
|
|
1986
|
-
import
|
|
1987
|
-
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) => {
|
|
1988
2138
|
const apiKey = getApiKey();
|
|
1989
2139
|
requireApiKey(apiKey);
|
|
1990
2140
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1992,61 +2142,61 @@ var usageCommand = new Command16("usage").description("Metered usage and cost fo
|
|
|
1992
2142
|
const appId = opts.app || getCurrentApp();
|
|
1993
2143
|
const report2 = await client4.apps.usage(appId, opts.period);
|
|
1994
2144
|
if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
|
|
1995
|
-
console.log(
|
|
2145
|
+
console.log(chalk18.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
|
|
1996
2146
|
console.log();
|
|
1997
2147
|
printMetrics(report2.metrics);
|
|
1998
2148
|
console.log();
|
|
1999
2149
|
for (const line of report2.lines) {
|
|
2000
|
-
console.log(` ${
|
|
2150
|
+
console.log(` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)}`);
|
|
2001
2151
|
}
|
|
2002
|
-
console.log(` ${
|
|
2152
|
+
console.log(` ${chalk18.bold("Share of the bill".padEnd(22))} ${chalk18.bold(report2.cost)}`);
|
|
2003
2153
|
console.log();
|
|
2004
2154
|
console.log(
|
|
2005
|
-
|
|
2155
|
+
chalk18.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
|
|
2006
2156
|
);
|
|
2007
2157
|
return;
|
|
2008
2158
|
}
|
|
2009
2159
|
const orgId = opts.org || getDefaultOrg();
|
|
2010
2160
|
if (!orgId) {
|
|
2011
|
-
console.error(
|
|
2161
|
+
console.error(chalk18.red("No org specified. Use --org, or --app for a single app."));
|
|
2012
2162
|
process.exit(1);
|
|
2013
2163
|
}
|
|
2014
2164
|
const report = await client4.orgs.usage(orgId, opts.period);
|
|
2015
2165
|
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
2016
2166
|
const inv = report.invoice;
|
|
2017
2167
|
console.log(
|
|
2018
|
-
|
|
2168
|
+
chalk18.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
|
|
2019
2169
|
);
|
|
2020
2170
|
console.log();
|
|
2021
2171
|
printMetrics(report.metrics);
|
|
2022
2172
|
if (report.apps.length > 0) {
|
|
2023
2173
|
console.log();
|
|
2024
|
-
console.log(
|
|
2174
|
+
console.log(chalk18.bold("By app"));
|
|
2025
2175
|
for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
|
|
2026
2176
|
const name = app.slug || app.name || app.appId || "(deleted apps)";
|
|
2027
|
-
console.log(` ${
|
|
2177
|
+
console.log(` ${chalk18.dim(String(name).padEnd(24))} ${money(app.cents)}`);
|
|
2028
2178
|
}
|
|
2029
2179
|
}
|
|
2030
2180
|
console.log();
|
|
2031
2181
|
for (const line of inv.lines) {
|
|
2032
2182
|
console.log(
|
|
2033
|
-
` ${
|
|
2183
|
+
` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk18.dim(
|
|
2034
2184
|
`(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
|
|
2035
2185
|
)}`
|
|
2036
2186
|
);
|
|
2037
2187
|
}
|
|
2038
|
-
console.log(` ${
|
|
2188
|
+
console.log(` ${chalk18.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
|
|
2039
2189
|
if (inv.includedUsageCents > 0) {
|
|
2040
2190
|
console.log(
|
|
2041
|
-
` ${
|
|
2191
|
+
` ${chalk18.dim("Included".padEnd(22))} ${chalk18.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
|
|
2042
2192
|
);
|
|
2043
2193
|
}
|
|
2044
2194
|
if (inv.baseCents > 0) {
|
|
2045
|
-
console.log(` ${
|
|
2195
|
+
console.log(` ${chalk18.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
|
|
2046
2196
|
}
|
|
2047
|
-
console.log(` ${
|
|
2197
|
+
console.log(` ${chalk18.bold("Total".padEnd(22))} ${chalk18.bold(report.total)}`);
|
|
2048
2198
|
console.log();
|
|
2049
|
-
console.log(
|
|
2199
|
+
console.log(chalk18.dim("Usage to date. The period is priced as it stands right now."));
|
|
2050
2200
|
});
|
|
2051
2201
|
usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
|
|
2052
2202
|
const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
|
|
@@ -2054,11 +2204,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2054
2204
|
if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
|
|
2055
2205
|
for (const plan of plans) {
|
|
2056
2206
|
console.log(
|
|
2057
|
-
`${
|
|
2207
|
+
`${chalk18.bold(plan.name)} ${chalk18.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk18.dim(`, ${plan.includedUsage} usage included`) : "")
|
|
2058
2208
|
);
|
|
2059
2209
|
const l = plan.limits;
|
|
2060
2210
|
console.log(
|
|
2061
|
-
|
|
2211
|
+
chalk18.dim(
|
|
2062
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" : ""}`
|
|
2063
2213
|
)
|
|
2064
2214
|
);
|
|
@@ -2067,11 +2217,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2067
2217
|
});
|
|
2068
2218
|
function printMetrics(metrics) {
|
|
2069
2219
|
if (metrics.length === 0) {
|
|
2070
|
-
console.log(
|
|
2220
|
+
console.log(chalk18.dim(" Nothing metered in this period yet."));
|
|
2071
2221
|
return;
|
|
2072
2222
|
}
|
|
2073
2223
|
for (const m of metrics) {
|
|
2074
|
-
console.log(` ${
|
|
2224
|
+
console.log(` ${chalk18.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
|
|
2075
2225
|
}
|
|
2076
2226
|
}
|
|
2077
2227
|
function round(n) {
|
|
@@ -2086,12 +2236,13 @@ function period(start, end) {
|
|
|
2086
2236
|
}
|
|
2087
2237
|
|
|
2088
2238
|
// src/index.ts
|
|
2089
|
-
var program = new
|
|
2239
|
+
var program = new Command18();
|
|
2090
2240
|
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.1.0");
|
|
2091
2241
|
program.addCommand(loginCommand);
|
|
2092
2242
|
program.addCommand(logoutCommand);
|
|
2093
2243
|
program.addCommand(initCommand);
|
|
2094
2244
|
program.addCommand(deployCommand);
|
|
2245
|
+
program.addCommand(pullCommand);
|
|
2095
2246
|
program.addCommand(logsCommand);
|
|
2096
2247
|
program.addCommand(metricsCommand);
|
|
2097
2248
|
program.addCommand(rollbackCommand);
|