@videan/cli 0.4.0 → 0.4.2
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 +324 -224
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { Command as Command18 } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/login.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import inquirer2 from "inquirer";
|
|
9
|
+
import chalk2 from "chalk";
|
|
10
10
|
import ora from "ora";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
|
|
@@ -733,7 +733,11 @@ var PLANS2 = {
|
|
|
733
733
|
includedUsageCents: 300,
|
|
734
734
|
maxApps: 3,
|
|
735
735
|
maxInstancesPerApp: 1,
|
|
736
|
-
//
|
|
736
|
+
// A *ceiling*, not a promise of free compute: a container task at even
|
|
737
|
+
// 256/512 running all month is ~$16 of usage against the $3 allowance.
|
|
738
|
+
// That is why apps created on this plan default to serverless mode, which
|
|
739
|
+
// meters nothing while idle -- the free tier is "free while it sleeps",
|
|
740
|
+
// and a container app on this plan is a deliberate, metered choice.
|
|
737
741
|
maxCpu: 512,
|
|
738
742
|
maxMemory: 1024,
|
|
739
743
|
maxStorageMb: 1024,
|
|
@@ -830,12 +834,94 @@ function clearConfig() {
|
|
|
830
834
|
config.clear();
|
|
831
835
|
}
|
|
832
836
|
|
|
837
|
+
// src/prompt.ts
|
|
838
|
+
import inquirer from "inquirer";
|
|
839
|
+
import chalk from "chalk";
|
|
840
|
+
async function prompt(questions, nonInteractiveHint) {
|
|
841
|
+
if (!process.stdin.isTTY) {
|
|
842
|
+
console.error(
|
|
843
|
+
chalk.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
|
|
844
|
+
);
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
return await inquirer.prompt(questions);
|
|
849
|
+
} catch (err) {
|
|
850
|
+
if (isStreamClosed(err)) {
|
|
851
|
+
console.error(chalk.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
throw err;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function isStreamClosed(err) {
|
|
858
|
+
const e = err;
|
|
859
|
+
return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
|
|
860
|
+
}
|
|
861
|
+
function installPromptCrashGuard() {
|
|
862
|
+
process.on("uncaughtException", (err) => {
|
|
863
|
+
if (isStreamClosed(err)) {
|
|
864
|
+
console.error(
|
|
865
|
+
chalk.red(
|
|
866
|
+
"Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
throw err;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
833
875
|
// src/commands/login.ts
|
|
876
|
+
async function completeTwoFactor(spinner, baseUrl, login) {
|
|
877
|
+
if (!process.stdin.isTTY) {
|
|
878
|
+
spinner.fail(
|
|
879
|
+
"This account has two-factor enabled: login needs a terminal to type the emailed code. Non-interactively, set VIDEAN_API_KEY instead (create a key from a terminal or the dashboard)."
|
|
880
|
+
);
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
spinner.stop();
|
|
884
|
+
console.log(
|
|
885
|
+
login.code_email_sent === false ? chalk2.yellow("Two-factor is on, but the code email could not be sent. Trying anyway \u2014 check your inbox.") : "A 6-digit sign-in code was emailed to you (it expires in 10 minutes)."
|
|
886
|
+
);
|
|
887
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
888
|
+
const { code } = await prompt(
|
|
889
|
+
[
|
|
890
|
+
{
|
|
891
|
+
type: "input",
|
|
892
|
+
name: "code",
|
|
893
|
+
message: "Sign-in code:",
|
|
894
|
+
validate: (v) => /^\d{6}$/.test(v.trim()) ? true : "The code is 6 digits"
|
|
895
|
+
}
|
|
896
|
+
],
|
|
897
|
+
"Two-factor login cannot be completed non-interactively; set VIDEAN_API_KEY instead."
|
|
898
|
+
);
|
|
899
|
+
let response;
|
|
900
|
+
try {
|
|
901
|
+
response = await fetch(`${baseUrl}/auth/login/2fa`, {
|
|
902
|
+
method: "POST",
|
|
903
|
+
headers: { "Content-Type": "application/json" },
|
|
904
|
+
body: JSON.stringify({ challenge: login.challenge, code: code.trim() })
|
|
905
|
+
});
|
|
906
|
+
} catch (e) {
|
|
907
|
+
spinner.fail(`Login failed: could not reach ${baseUrl} (${e?.message || e})`);
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
if (response.ok) {
|
|
911
|
+
const data = await response.json();
|
|
912
|
+
return data.data.token;
|
|
913
|
+
}
|
|
914
|
+
const body = await response.json().catch(() => null);
|
|
915
|
+
console.error(chalk2.red(body?.error?.message || "Invalid or expired sign-in code"));
|
|
916
|
+
}
|
|
917
|
+
spinner.fail("Too many wrong codes. Run `videan login` again to get a new one.");
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
834
920
|
var loginCommand = new Command("login").description("Log in to Cloud").option("-e, --email <email>", "Email (skips the prompt)").option("-p, --password <password>", "Password (skips the prompt; prefer VIDEAN_PASSWORD)").option("-o, --org <id>", "Organization to scope the key to (skips the picker)").option("--api-url <url>", "API base URL to use and remember (default: the public API)").action(async (opts) => {
|
|
835
921
|
const password = opts.password || process.env.VIDEAN_PASSWORD || process.env.CLOUD_PASSWORD;
|
|
836
922
|
if ((!opts.email || !password) && !process.stdin.isTTY) {
|
|
837
923
|
console.error(
|
|
838
|
-
|
|
924
|
+
chalk2.red(
|
|
839
925
|
"login needs a terminal to prompt. Non-interactively, pass --email and --password (or VIDEAN_PASSWORD), or skip login entirely by setting VIDEAN_API_KEY."
|
|
840
926
|
)
|
|
841
927
|
);
|
|
@@ -848,12 +934,13 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
848
934
|
const answers = {
|
|
849
935
|
email: opts.email,
|
|
850
936
|
password,
|
|
851
|
-
...prompts.length ? await
|
|
937
|
+
...prompts.length ? await inquirer2.prompt(prompts) : {}
|
|
852
938
|
};
|
|
853
939
|
if (opts.apiUrl) setBaseUrl(opts.apiUrl.replace(/\/$/, ""));
|
|
854
940
|
const baseUrl = (getBaseUrl() || DEFAULT_API_URL2).replace(/\/$/, "");
|
|
855
941
|
const spinner = ora("Logging in...").start();
|
|
856
942
|
let token;
|
|
943
|
+
let login;
|
|
857
944
|
try {
|
|
858
945
|
const response = await fetch(`${baseUrl}/auth/login`, {
|
|
859
946
|
method: "POST",
|
|
@@ -866,17 +953,22 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
866
953
|
} else {
|
|
867
954
|
spinner.fail(`Login failed: ${response.status} from ${baseUrl}/auth/login`);
|
|
868
955
|
console.error(
|
|
869
|
-
|
|
956
|
+
chalk2.gray(" Wrong API URL? Re-run with --api-url <url>, or `videan logout` to reset.")
|
|
870
957
|
);
|
|
871
958
|
}
|
|
872
959
|
process.exit(1);
|
|
873
960
|
}
|
|
874
961
|
const data = await response.json();
|
|
875
|
-
|
|
962
|
+
login = data.data;
|
|
876
963
|
} catch (e) {
|
|
877
964
|
spinner.fail(`Login failed: could not reach ${baseUrl} (${e?.message || e})`);
|
|
878
965
|
process.exit(1);
|
|
879
966
|
}
|
|
967
|
+
if (login.requires_2fa) {
|
|
968
|
+
token = await completeTwoFactor(spinner, baseUrl, login);
|
|
969
|
+
} else {
|
|
970
|
+
token = login.token;
|
|
971
|
+
}
|
|
880
972
|
const session = new CloudClient({ apiKey: token, baseUrl });
|
|
881
973
|
let orgId;
|
|
882
974
|
try {
|
|
@@ -895,7 +987,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
895
987
|
process.exit(1);
|
|
896
988
|
} else if (orgs.length > 1) {
|
|
897
989
|
spinner.stop();
|
|
898
|
-
const pick = await
|
|
990
|
+
const pick = await inquirer2.prompt([
|
|
899
991
|
{
|
|
900
992
|
type: "list",
|
|
901
993
|
name: "orgId",
|
|
@@ -915,7 +1007,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
915
1007
|
if (!orgId) {
|
|
916
1008
|
setApiKey(token);
|
|
917
1009
|
spinner.warn(
|
|
918
|
-
|
|
1010
|
+
chalk2.yellow("Logged in, but you have no organization yet. Run `videan orgs create <name>`.")
|
|
919
1011
|
);
|
|
920
1012
|
return;
|
|
921
1013
|
}
|
|
@@ -926,32 +1018,32 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
926
1018
|
});
|
|
927
1019
|
setApiKey(key.key);
|
|
928
1020
|
setDefaultOrg(orgId);
|
|
929
|
-
spinner.succeed(
|
|
930
|
-
console.log(
|
|
931
|
-
console.log(
|
|
1021
|
+
spinner.succeed(chalk2.green("Logged in."));
|
|
1022
|
+
console.log(chalk2.gray(` Org: ${orgId}`));
|
|
1023
|
+
console.log(chalk2.gray(` Key: ${key.keyPrefix}\u2026 (revoke with \`videan keys revoke ${key.id}\`)`));
|
|
932
1024
|
} catch (e) {
|
|
933
1025
|
setApiKey(token);
|
|
934
1026
|
setDefaultOrg(orgId);
|
|
935
1027
|
spinner.warn(
|
|
936
|
-
|
|
1028
|
+
chalk2.yellow(`Logged in with a temporary session (key creation failed: ${e?.message}).`)
|
|
937
1029
|
);
|
|
938
1030
|
}
|
|
939
1031
|
});
|
|
940
1032
|
|
|
941
1033
|
// src/commands/logout.ts
|
|
942
1034
|
import { Command as Command2 } from "commander";
|
|
943
|
-
import
|
|
1035
|
+
import chalk3 from "chalk";
|
|
944
1036
|
var logoutCommand = new Command2("logout").description("Forget the saved key, org, app and API URL").action(() => {
|
|
945
1037
|
const where = configPath();
|
|
946
1038
|
clearConfig();
|
|
947
|
-
console.log(
|
|
948
|
-
console.log(
|
|
1039
|
+
console.log(chalk3.green("Logged out."));
|
|
1040
|
+
console.log(chalk3.gray(` Cleared ${where}`));
|
|
949
1041
|
});
|
|
950
1042
|
|
|
951
1043
|
// src/commands/init.ts
|
|
952
1044
|
import { Command as Command3 } from "commander";
|
|
953
|
-
import
|
|
954
|
-
import
|
|
1045
|
+
import inquirer3 from "inquirer";
|
|
1046
|
+
import chalk4 from "chalk";
|
|
955
1047
|
import fs2 from "fs-extra";
|
|
956
1048
|
import path2 from "node:path";
|
|
957
1049
|
|
|
@@ -1018,20 +1110,20 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
|
|
|
1018
1110
|
const interactive = process.stdin.isTTY && !opts.yes;
|
|
1019
1111
|
if (await fs2.pathExists(configPath2)) {
|
|
1020
1112
|
if (!interactive) {
|
|
1021
|
-
console.log(
|
|
1113
|
+
console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
|
|
1022
1114
|
} else {
|
|
1023
|
-
console.log(
|
|
1024
|
-
const { overwrite } = await
|
|
1115
|
+
console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
|
|
1116
|
+
const { overwrite } = await inquirer3.prompt([
|
|
1025
1117
|
{ type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
|
|
1026
1118
|
]);
|
|
1027
1119
|
if (!overwrite) {
|
|
1028
|
-
for (const f of await writeVideanMd(process.cwd())) console.log(
|
|
1120
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk4.green(`Wrote ${f}`));
|
|
1029
1121
|
return;
|
|
1030
1122
|
}
|
|
1031
1123
|
}
|
|
1032
1124
|
}
|
|
1033
1125
|
if (!await fs2.pathExists(configPath2) || interactive) {
|
|
1034
|
-
const answers = interactive ? await
|
|
1126
|
+
const answers = interactive ? await inquirer3.prompt([
|
|
1035
1127
|
{ type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
|
|
1036
1128
|
{
|
|
1037
1129
|
type: "list",
|
|
@@ -1052,21 +1144,21 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
|
|
|
1052
1144
|
""
|
|
1053
1145
|
].join("\n");
|
|
1054
1146
|
await fs2.writeFile(configPath2, config2, "utf-8");
|
|
1055
|
-
console.log(
|
|
1147
|
+
console.log(chalk4.green(`Created ${CONFIG_FILE_NAME}`));
|
|
1056
1148
|
}
|
|
1057
|
-
for (const f of await writeVideanMd(process.cwd())) console.log(
|
|
1149
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk4.green(`Wrote ${f}`));
|
|
1058
1150
|
});
|
|
1059
1151
|
|
|
1060
1152
|
// src/commands/deploy.ts
|
|
1061
1153
|
import { Command as Command4 } from "commander";
|
|
1062
|
-
import
|
|
1154
|
+
import chalk6 from "chalk";
|
|
1063
1155
|
import ora2 from "ora";
|
|
1064
1156
|
|
|
1065
1157
|
// src/util.ts
|
|
1066
1158
|
import fs3 from "fs-extra";
|
|
1067
1159
|
import path3 from "node:path";
|
|
1068
1160
|
import ignore from "ignore";
|
|
1069
|
-
import
|
|
1161
|
+
import chalk5 from "chalk";
|
|
1070
1162
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
1071
1163
|
async function collectFiles(dir) {
|
|
1072
1164
|
const ig = ignore();
|
|
@@ -1127,15 +1219,15 @@ async function collectFiles(dir) {
|
|
|
1127
1219
|
function formatStatus(status) {
|
|
1128
1220
|
switch (status) {
|
|
1129
1221
|
case "running":
|
|
1130
|
-
return
|
|
1222
|
+
return chalk5.green(status);
|
|
1131
1223
|
case "building":
|
|
1132
1224
|
case "deploying":
|
|
1133
|
-
return
|
|
1225
|
+
return chalk5.yellow(status);
|
|
1134
1226
|
case "failed":
|
|
1135
1227
|
case "crashed":
|
|
1136
|
-
return
|
|
1228
|
+
return chalk5.red(status);
|
|
1137
1229
|
case "stopped":
|
|
1138
|
-
return
|
|
1230
|
+
return chalk5.gray(status);
|
|
1139
1231
|
default:
|
|
1140
1232
|
return status;
|
|
1141
1233
|
}
|
|
@@ -1145,13 +1237,13 @@ function formatRow(columns, widths) {
|
|
|
1145
1237
|
}
|
|
1146
1238
|
function requireApiKey(apiKey) {
|
|
1147
1239
|
if (!apiKey) {
|
|
1148
|
-
console.error(
|
|
1240
|
+
console.error(chalk5.red("Not logged in. Run `videan login` first."));
|
|
1149
1241
|
process.exit(1);
|
|
1150
1242
|
}
|
|
1151
1243
|
}
|
|
1152
1244
|
function requireOrg(orgId) {
|
|
1153
1245
|
if (!orgId) {
|
|
1154
|
-
console.error(
|
|
1246
|
+
console.error(chalk5.red("No organization. Pass --org <id> or run `videan login` again."));
|
|
1155
1247
|
process.exit(1);
|
|
1156
1248
|
}
|
|
1157
1249
|
return orgId;
|
|
@@ -1163,7 +1255,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1163
1255
|
requireApiKey(apiKey);
|
|
1164
1256
|
const appId = opts.app || getCurrentApp();
|
|
1165
1257
|
if (!appId) {
|
|
1166
|
-
console.error(
|
|
1258
|
+
console.error(chalk6.red("No app specified. Use --app or run `videan init` first."));
|
|
1167
1259
|
process.exit(1);
|
|
1168
1260
|
}
|
|
1169
1261
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1172,19 +1264,19 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1172
1264
|
const binaryCount = Object.keys(binaryFiles).length;
|
|
1173
1265
|
const fileCount = Object.keys(files).length + binaryCount;
|
|
1174
1266
|
if (!fileCount) {
|
|
1175
|
-
spinner.fail(
|
|
1267
|
+
spinner.fail(chalk6.red(`Nothing to deploy: no files under ${process.cwd()}.`));
|
|
1176
1268
|
process.exit(1);
|
|
1177
1269
|
}
|
|
1178
1270
|
if (skipped.length) {
|
|
1179
|
-
spinner.warn(
|
|
1180
|
-
for (const file of skipped) console.log(
|
|
1271
|
+
spinner.warn(chalk6.yellow(`Skipped ${skipped.length} file(s):`));
|
|
1272
|
+
for (const file of skipped) console.log(chalk6.gray(` ${file.path} \u2014 ${file.reason}`));
|
|
1181
1273
|
spinner.start();
|
|
1182
1274
|
}
|
|
1183
1275
|
spinner.text = `Pushing ${fileCount} files${binaryCount ? ` (${binaryCount} binary)` : ""}...`;
|
|
1184
1276
|
const snapshot = await client4.apps.push(appId, files, opts.message, binaryFiles);
|
|
1185
1277
|
spinner.text = "Triggering deployment...";
|
|
1186
1278
|
if (opts.env && opts.env !== "preview" && opts.env !== "production") {
|
|
1187
|
-
spinner.fail(
|
|
1279
|
+
spinner.fail(chalk6.red(`--env must be preview or production, not ${opts.env}`));
|
|
1188
1280
|
process.exit(1);
|
|
1189
1281
|
}
|
|
1190
1282
|
const deployment = await client4.apps.deploy(appId, snapshot.id, opts.env);
|
|
@@ -1202,34 +1294,34 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1202
1294
|
} catch (e) {
|
|
1203
1295
|
lastError = e?.message || String(e);
|
|
1204
1296
|
if (++consecutiveErrors >= 5) {
|
|
1205
|
-
spinner.fail(
|
|
1206
|
-
console.log(
|
|
1297
|
+
spinner.fail(chalk6.red(`Lost track of the deployment: ${lastError}`));
|
|
1298
|
+
console.log(chalk6.gray(` It may still finish. Check with \`videan status\`.`));
|
|
1207
1299
|
process.exit(1);
|
|
1208
1300
|
}
|
|
1209
1301
|
}
|
|
1210
1302
|
}
|
|
1211
1303
|
if (current.status === "running") {
|
|
1212
|
-
spinner.succeed(
|
|
1304
|
+
spinner.succeed(chalk6.green(`Deployed successfully!`));
|
|
1213
1305
|
try {
|
|
1214
1306
|
const app = await client4.apps.get(appId);
|
|
1215
1307
|
const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
|
|
1216
1308
|
console.log(
|
|
1217
|
-
gated ?
|
|
1309
|
+
gated ? chalk6.gray(
|
|
1218
1310
|
` 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.`
|
|
1219
|
-
) :
|
|
1311
|
+
) : chalk6.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
|
|
1220
1312
|
);
|
|
1221
1313
|
} catch {
|
|
1222
1314
|
}
|
|
1223
1315
|
if (current.url) {
|
|
1224
1316
|
const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
|
|
1225
|
-
console.log(
|
|
1317
|
+
console.log(chalk6.cyan(` URL: ${url}`));
|
|
1226
1318
|
}
|
|
1227
1319
|
if (current.previewUrl) {
|
|
1228
|
-
console.log(
|
|
1320
|
+
console.log(chalk6.gray(` This version: ${current.previewUrl}`));
|
|
1229
1321
|
}
|
|
1230
1322
|
} else if (current.status === "failed" || current.status === "crashed") {
|
|
1231
|
-
spinner.fail(
|
|
1232
|
-
console.log(
|
|
1323
|
+
spinner.fail(chalk6.red(`Deployment ${current.status}: ${current.error || "unknown error"}`));
|
|
1324
|
+
console.log(chalk6.gray(` Logs: videan logs --app ${appId}`));
|
|
1233
1325
|
process.exit(1);
|
|
1234
1326
|
} else {
|
|
1235
1327
|
spinner.info(`Deployment status: ${formatStatus(current.status)}`);
|
|
@@ -1239,7 +1331,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1239
1331
|
|
|
1240
1332
|
// src/commands/pull.ts
|
|
1241
1333
|
import { Command as Command5 } from "commander";
|
|
1242
|
-
import
|
|
1334
|
+
import chalk7 from "chalk";
|
|
1243
1335
|
import ora3 from "ora";
|
|
1244
1336
|
import fs4 from "fs-extra";
|
|
1245
1337
|
import path4 from "node:path";
|
|
@@ -1263,14 +1355,14 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1263
1355
|
if (!snapshotId) {
|
|
1264
1356
|
const appId = opts.app || getCurrentApp();
|
|
1265
1357
|
if (!appId) {
|
|
1266
|
-
spinner.fail(
|
|
1358
|
+
spinner.fail(chalk7.red("No app specified. Use --app, VIDEAN_APP_ID, or name a snapshot id."));
|
|
1267
1359
|
process.exit(1);
|
|
1268
1360
|
}
|
|
1269
1361
|
const [latest] = await get(
|
|
1270
1362
|
`/v1/apps/${appId}/snapshots?limit=1`
|
|
1271
1363
|
);
|
|
1272
1364
|
if (!latest) {
|
|
1273
|
-
spinner.fail(
|
|
1365
|
+
spinner.fail(chalk7.red("This app has no snapshots yet."));
|
|
1274
1366
|
process.exit(1);
|
|
1275
1367
|
}
|
|
1276
1368
|
snapshotId = latest.id;
|
|
@@ -1282,7 +1374,7 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1282
1374
|
await fs4.ensureDir(outDir);
|
|
1283
1375
|
const existing = await fs4.readdir(outDir);
|
|
1284
1376
|
if (existing.length > 0 && !opts.force) {
|
|
1285
|
-
spinner.fail(
|
|
1377
|
+
spinner.fail(chalk7.red(`${outDir} is not empty. Pass --force to write into it anyway.`));
|
|
1286
1378
|
process.exit(1);
|
|
1287
1379
|
}
|
|
1288
1380
|
spinner.text = `Pulling ${detail.manifest.length} files...`;
|
|
@@ -1302,29 +1394,29 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1302
1394
|
spinner.text = `Pulling ${written}/${detail.manifest.length}...`;
|
|
1303
1395
|
}
|
|
1304
1396
|
spinner.succeed(
|
|
1305
|
-
|
|
1397
|
+
chalk7.green(
|
|
1306
1398
|
`Pulled snapshot ${detail.seq ? `#${detail.seq}` : snapshotId} \u2014 ${written} files into ${outDir}`
|
|
1307
1399
|
)
|
|
1308
1400
|
);
|
|
1309
1401
|
} catch (err) {
|
|
1310
|
-
spinner.fail(
|
|
1402
|
+
spinner.fail(chalk7.red(err instanceof Error ? err.message : String(err)));
|
|
1311
1403
|
process.exit(1);
|
|
1312
1404
|
}
|
|
1313
1405
|
});
|
|
1314
1406
|
|
|
1315
1407
|
// src/commands/logs.ts
|
|
1316
1408
|
import { Command as Command6 } from "commander";
|
|
1317
|
-
import
|
|
1409
|
+
import chalk8 from "chalk";
|
|
1318
1410
|
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) => {
|
|
1319
1411
|
const apiKey = getApiKey();
|
|
1320
1412
|
requireApiKey(apiKey);
|
|
1321
1413
|
const appId = opts.app || getCurrentApp();
|
|
1322
1414
|
if (!appId) {
|
|
1323
|
-
console.error(
|
|
1415
|
+
console.error(chalk8.red("No app specified. Use --app or link an app."));
|
|
1324
1416
|
process.exit(1);
|
|
1325
1417
|
}
|
|
1326
1418
|
if (opts.search && opts.pattern) {
|
|
1327
|
-
console.error(
|
|
1419
|
+
console.error(chalk8.red("Use either --search or --pattern, not both."));
|
|
1328
1420
|
process.exit(1);
|
|
1329
1421
|
}
|
|
1330
1422
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1347,43 +1439,43 @@ var logsCommand = new Command6("logs").description("View app logs").option("-a,
|
|
|
1347
1439
|
});
|
|
1348
1440
|
for (const entry of page.events) print(entry);
|
|
1349
1441
|
if (page.events.length === 0) {
|
|
1350
|
-
console.log(
|
|
1442
|
+
console.log(chalk8.gray(`No log lines since ${page.start}.`));
|
|
1351
1443
|
}
|
|
1352
1444
|
if (page.truncated) {
|
|
1353
1445
|
console.log(
|
|
1354
|
-
|
|
1446
|
+
chalk8.yellow(
|
|
1355
1447
|
"\nThis is part of the window -- there are more lines than one request reads. Narrow --since or add --search."
|
|
1356
1448
|
)
|
|
1357
1449
|
);
|
|
1358
1450
|
}
|
|
1359
1451
|
});
|
|
1360
1452
|
function print(entry) {
|
|
1361
|
-
console.log(`${
|
|
1453
|
+
console.log(`${chalk8.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
|
|
1362
1454
|
}
|
|
1363
1455
|
function formatLevel(level) {
|
|
1364
1456
|
switch (level) {
|
|
1365
1457
|
case "error":
|
|
1366
|
-
return
|
|
1458
|
+
return chalk8.red("[ERR]");
|
|
1367
1459
|
case "warn":
|
|
1368
|
-
return
|
|
1460
|
+
return chalk8.yellow("[WRN]");
|
|
1369
1461
|
case "info":
|
|
1370
|
-
return
|
|
1462
|
+
return chalk8.blue("[INF]");
|
|
1371
1463
|
case "debug":
|
|
1372
|
-
return
|
|
1464
|
+
return chalk8.gray("[DBG]");
|
|
1373
1465
|
default:
|
|
1374
|
-
return
|
|
1466
|
+
return chalk8.gray("[LOG]");
|
|
1375
1467
|
}
|
|
1376
1468
|
}
|
|
1377
1469
|
|
|
1378
1470
|
// src/commands/metrics.ts
|
|
1379
1471
|
import { Command as Command7 } from "commander";
|
|
1380
|
-
import
|
|
1472
|
+
import chalk9 from "chalk";
|
|
1381
1473
|
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) => {
|
|
1382
1474
|
const apiKey = getApiKey();
|
|
1383
1475
|
requireApiKey(apiKey);
|
|
1384
1476
|
const appId = opts.app || getCurrentApp();
|
|
1385
1477
|
if (!appId) {
|
|
1386
|
-
console.error(
|
|
1478
|
+
console.error(chalk9.red("No app specified. Use --app or link an app."));
|
|
1387
1479
|
process.exit(1);
|
|
1388
1480
|
}
|
|
1389
1481
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1394,7 +1486,7 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
|
|
|
1394
1486
|
}
|
|
1395
1487
|
const t = metrics.totals;
|
|
1396
1488
|
console.log(
|
|
1397
|
-
|
|
1489
|
+
chalk9.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
|
|
1398
1490
|
);
|
|
1399
1491
|
console.log();
|
|
1400
1492
|
console.log(` ${label("Requests")} ${t.requests}`);
|
|
@@ -1402,100 +1494,100 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
|
|
|
1402
1494
|
console.log(` ${label("5xx")} ${count(t.serverErrors, true)}`);
|
|
1403
1495
|
console.log(` ${label("Gate denied")} ${count(t.gateDenied)}`);
|
|
1404
1496
|
console.log(` ${label("Latency avg")} ${ms(t.latencyAvgMs)}`);
|
|
1405
|
-
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${
|
|
1497
|
+
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk9.dim("(sampled)")}`);
|
|
1406
1498
|
console.log(` ${label("CPU avg")} ${units(t.cpuAvg)}`);
|
|
1407
1499
|
console.log(` ${label("Memory avg")} ${mb(t.memoryAvgMb)}`);
|
|
1408
1500
|
if (t.requests === 0) {
|
|
1409
1501
|
console.log();
|
|
1410
|
-
console.log(
|
|
1502
|
+
console.log(chalk9.dim("No requests recorded in this window."));
|
|
1411
1503
|
}
|
|
1412
1504
|
if (opts.series) {
|
|
1413
1505
|
console.log();
|
|
1414
1506
|
for (const [name, points] of Object.entries(metrics.series)) {
|
|
1415
1507
|
if (points.length === 0) continue;
|
|
1416
|
-
console.log(
|
|
1417
|
-
for (const p of points) console.log(` ${
|
|
1508
|
+
console.log(chalk9.bold(name));
|
|
1509
|
+
for (const p of points) console.log(` ${chalk9.dim(p.t)} ${p.v}`);
|
|
1418
1510
|
}
|
|
1419
1511
|
}
|
|
1420
1512
|
});
|
|
1421
1513
|
function label(text) {
|
|
1422
|
-
return
|
|
1514
|
+
return chalk9.dim(text.padEnd(13));
|
|
1423
1515
|
}
|
|
1424
1516
|
function count(n, severe = false) {
|
|
1425
|
-
if (n === 0) return
|
|
1426
|
-
return severe ?
|
|
1517
|
+
if (n === 0) return chalk9.dim("0");
|
|
1518
|
+
return severe ? chalk9.red(String(n)) : chalk9.yellow(String(n));
|
|
1427
1519
|
}
|
|
1428
1520
|
function ms(v) {
|
|
1429
|
-
return v === null ?
|
|
1521
|
+
return v === null ? chalk9.dim("\u2014") : `${v} ms`;
|
|
1430
1522
|
}
|
|
1431
1523
|
function mb(v) {
|
|
1432
|
-
return v === null ?
|
|
1524
|
+
return v === null ? chalk9.dim("\u2014") : `${v} MB`;
|
|
1433
1525
|
}
|
|
1434
1526
|
function units(v) {
|
|
1435
|
-
return v === null ?
|
|
1527
|
+
return v === null ? chalk9.dim("\u2014") : `${v} CPU units`;
|
|
1436
1528
|
}
|
|
1437
1529
|
|
|
1438
1530
|
// src/commands/rollback.ts
|
|
1439
1531
|
import { Command as Command8 } from "commander";
|
|
1440
|
-
import
|
|
1532
|
+
import chalk10 from "chalk";
|
|
1441
1533
|
import ora4 from "ora";
|
|
1442
1534
|
var rollbackCommand = new Command8("rollback").description("Rollback to the previous production deployment").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1443
1535
|
const apiKey = getApiKey();
|
|
1444
1536
|
requireApiKey(apiKey);
|
|
1445
1537
|
const appId = opts.app || getCurrentApp();
|
|
1446
1538
|
if (!appId) {
|
|
1447
|
-
console.error(
|
|
1539
|
+
console.error(chalk10.red("No app specified. Use --app or link an app."));
|
|
1448
1540
|
process.exit(1);
|
|
1449
1541
|
}
|
|
1450
1542
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1451
1543
|
const spinner = ora4("Rolling back...").start();
|
|
1452
1544
|
try {
|
|
1453
1545
|
const deployment = await client4.apps.rollback(appId);
|
|
1454
|
-
spinner.succeed(
|
|
1546
|
+
spinner.succeed(chalk10.green("Rollback successful!"));
|
|
1455
1547
|
if (deployment.url) {
|
|
1456
|
-
console.log(
|
|
1548
|
+
console.log(chalk10.cyan(` URL: ${deployment.url}`));
|
|
1457
1549
|
}
|
|
1458
1550
|
} catch (error) {
|
|
1459
|
-
spinner.fail(
|
|
1551
|
+
spinner.fail(chalk10.red(`Rollback failed: ${error.message}`));
|
|
1460
1552
|
process.exit(1);
|
|
1461
1553
|
}
|
|
1462
1554
|
});
|
|
1463
1555
|
|
|
1464
1556
|
// src/commands/secrets.ts
|
|
1465
1557
|
import { Command as Command9 } from "commander";
|
|
1466
|
-
import
|
|
1467
|
-
import inquirer3 from "inquirer";
|
|
1558
|
+
import chalk11 from "chalk";
|
|
1468
1559
|
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1469
1560
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1470
1561
|
const apiKey = getApiKey();
|
|
1471
1562
|
requireApiKey(apiKey);
|
|
1472
1563
|
const appId = opts.app || getCurrentApp();
|
|
1473
1564
|
if (!appId) {
|
|
1474
|
-
console.error(
|
|
1565
|
+
console.error(chalk11.red("No app specified."));
|
|
1475
1566
|
process.exit(1);
|
|
1476
1567
|
}
|
|
1477
1568
|
if (!value) {
|
|
1478
|
-
const answers = await
|
|
1479
|
-
{ type: "password", name: "value", message: `Value for ${key}:` }
|
|
1480
|
-
|
|
1569
|
+
const answers = await prompt(
|
|
1570
|
+
[{ type: "password", name: "value", message: `Value for ${key}:` }],
|
|
1571
|
+
`Pass the value as an argument: videan secrets set ${key} <value>`
|
|
1572
|
+
);
|
|
1481
1573
|
value = answers.value;
|
|
1482
1574
|
}
|
|
1483
1575
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1484
1576
|
await client4.secrets.set(appId, key, value);
|
|
1485
|
-
console.log(
|
|
1577
|
+
console.log(chalk11.green(`Secret "${key}" set successfully.`));
|
|
1486
1578
|
});
|
|
1487
1579
|
secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1488
1580
|
const apiKey = getApiKey();
|
|
1489
1581
|
requireApiKey(apiKey);
|
|
1490
1582
|
const appId = opts.app || getCurrentApp();
|
|
1491
1583
|
if (!appId) {
|
|
1492
|
-
console.error(
|
|
1584
|
+
console.error(chalk11.red("No app specified."));
|
|
1493
1585
|
process.exit(1);
|
|
1494
1586
|
}
|
|
1495
1587
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1496
1588
|
const secrets = await client4.secrets.list(appId);
|
|
1497
1589
|
if (secrets.length === 0) {
|
|
1498
|
-
console.log(
|
|
1590
|
+
console.log(chalk11.gray("No secrets configured."));
|
|
1499
1591
|
return;
|
|
1500
1592
|
}
|
|
1501
1593
|
console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
|
|
@@ -1508,24 +1600,23 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
|
|
|
1508
1600
|
requireApiKey(apiKey);
|
|
1509
1601
|
const appId = opts.app || getCurrentApp();
|
|
1510
1602
|
if (!appId) {
|
|
1511
|
-
console.error(
|
|
1603
|
+
console.error(chalk11.red("No app specified."));
|
|
1512
1604
|
process.exit(1);
|
|
1513
1605
|
}
|
|
1514
1606
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1515
1607
|
await client4.secrets.delete(appId, key);
|
|
1516
|
-
console.log(
|
|
1608
|
+
console.log(chalk11.green(`Secret "${key}" removed.`));
|
|
1517
1609
|
});
|
|
1518
1610
|
|
|
1519
1611
|
// src/commands/apps.ts
|
|
1520
1612
|
import { Command as Command10 } from "commander";
|
|
1521
|
-
import
|
|
1522
|
-
import inquirer4 from "inquirer";
|
|
1613
|
+
import chalk12 from "chalk";
|
|
1523
1614
|
var appsCommand = new Command10("apps").description("Manage apps");
|
|
1524
1615
|
function accessOption(value) {
|
|
1525
1616
|
if (value === void 0) return void 0;
|
|
1526
1617
|
const tier = value.trim().toLowerCase();
|
|
1527
1618
|
if (!isVisibility(tier)) {
|
|
1528
|
-
console.error(
|
|
1619
|
+
console.error(chalk12.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
|
|
1529
1620
|
process.exit(1);
|
|
1530
1621
|
}
|
|
1531
1622
|
return tier;
|
|
@@ -1534,7 +1625,7 @@ function modeOption(value) {
|
|
|
1534
1625
|
if (value === void 0) return void 0;
|
|
1535
1626
|
const mode = value.trim().toLowerCase();
|
|
1536
1627
|
if (!isAppMode(mode)) {
|
|
1537
|
-
console.error(
|
|
1628
|
+
console.error(chalk12.red(`Unknown --mode value "${value}". Use one of: ${APP_MODES.join(", ")}`));
|
|
1538
1629
|
process.exit(1);
|
|
1539
1630
|
}
|
|
1540
1631
|
return mode;
|
|
@@ -1550,7 +1641,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
|
|
|
1550
1641
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1551
1642
|
const apps = await client4.apps.list(orgId);
|
|
1552
1643
|
if (apps.length === 0) {
|
|
1553
|
-
console.log(
|
|
1644
|
+
console.log(chalk12.gray("No apps yet. Create one with `videan apps create`."));
|
|
1554
1645
|
return;
|
|
1555
1646
|
}
|
|
1556
1647
|
const widths = [20, 24, 10, 44];
|
|
@@ -1572,13 +1663,14 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1572
1663
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1573
1664
|
let name = opts.name;
|
|
1574
1665
|
if (!name) {
|
|
1575
|
-
const answers = await
|
|
1576
|
-
{ type: "input", name: "name", message: "App name:" }
|
|
1577
|
-
|
|
1666
|
+
const answers = await prompt(
|
|
1667
|
+
[{ type: "input", name: "name", message: "App name:" }],
|
|
1668
|
+
"Pass the name: videan apps create --name <name>"
|
|
1669
|
+
);
|
|
1578
1670
|
name = answers.name;
|
|
1579
1671
|
}
|
|
1580
1672
|
if (!name) {
|
|
1581
|
-
console.error(
|
|
1673
|
+
console.error(chalk12.red("An app name is required."));
|
|
1582
1674
|
process.exit(1);
|
|
1583
1675
|
}
|
|
1584
1676
|
const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
|
|
@@ -1595,11 +1687,11 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
|
|
|
1595
1687
|
mode
|
|
1596
1688
|
});
|
|
1597
1689
|
setCurrentApp(app.id);
|
|
1598
|
-
console.log(
|
|
1599
|
-
console.log(
|
|
1600
|
-
console.log(
|
|
1601
|
-
console.log(
|
|
1602
|
-
console.log(
|
|
1690
|
+
console.log(chalk12.green(`App "${app.name}" created (${app.id})`));
|
|
1691
|
+
console.log(chalk12.gray(` Mode: ${app.mode ?? "container"}`));
|
|
1692
|
+
console.log(chalk12.gray(` Access: ${describeAccess(app)}`));
|
|
1693
|
+
console.log(chalk12.gray(` URL after first deploy: ${appUrl(app.slug)}`));
|
|
1694
|
+
console.log(chalk12.gray(" Push code with `videan deploy` \u2014 the runtime is auto-detected."));
|
|
1603
1695
|
}
|
|
1604
1696
|
);
|
|
1605
1697
|
appsCommand.command("set").description("Change an app's settings (scale, rate limit, gate, capabilities)").option("-a, --app <id>", "App ID").option("-n, --name <name>", "Rename the app (the URL keeps its original slug)").option("--cpu <units>", "CPU units per instance (256 = 0.25 vCPU)").option("--memory <mib>", "Memory per instance, in MiB").option("--instances <n>", "Number of instances to run").option("--rate-limit <rpm>", "Requests per minute per client").option("--no-rate-limit", "Remove the rate limit").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")}`).option("--public", "Serve without the platform login gate (same as --access public)").option("--private", "Require platform login (leaves the tier alone if it is already gated)").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--stack <label>", "Group this app with its siblings under a shared label").option("--no-stack", "Remove the app from its stack").option("--mode <mode>", `How the app runs: ${APP_MODES.join(" | ")} (applies on the next deploy or restart)`).option("--json", "Print the raw response").action(
|
|
@@ -1608,16 +1700,16 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1608
1700
|
requireApiKey(apiKey);
|
|
1609
1701
|
const appId = opts.app || getCurrentApp();
|
|
1610
1702
|
if (!appId) {
|
|
1611
|
-
console.error(
|
|
1703
|
+
console.error(chalk12.red("No app specified. Use --app or link an app."));
|
|
1612
1704
|
process.exit(1);
|
|
1613
1705
|
}
|
|
1614
1706
|
if (opts.public && opts.private) {
|
|
1615
|
-
console.error(
|
|
1707
|
+
console.error(chalk12.red("--public and --private contradict each other."));
|
|
1616
1708
|
process.exit(1);
|
|
1617
1709
|
}
|
|
1618
1710
|
const access = accessOption(opts.access);
|
|
1619
1711
|
if (access && (opts.public || opts.private)) {
|
|
1620
|
-
console.error(
|
|
1712
|
+
console.error(chalk12.red("--access already says who may open the app; drop --public/--private."));
|
|
1621
1713
|
process.exit(1);
|
|
1622
1714
|
}
|
|
1623
1715
|
const mode = modeOption(opts.mode);
|
|
@@ -1642,7 +1734,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1642
1734
|
if (opts.stack === false) changes.stack = null;
|
|
1643
1735
|
else if (typeof opts.stack === "string") changes.stack = opts.stack;
|
|
1644
1736
|
if (Object.keys(changes).length === 0) {
|
|
1645
|
-
console.error(
|
|
1737
|
+
console.error(chalk12.red("Nothing to change. Pass at least one option."));
|
|
1646
1738
|
process.exit(1);
|
|
1647
1739
|
}
|
|
1648
1740
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1651,7 +1743,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1651
1743
|
console.log(JSON.stringify(app, null, 2));
|
|
1652
1744
|
return;
|
|
1653
1745
|
}
|
|
1654
|
-
console.log(
|
|
1746
|
+
console.log(chalk12.green(`Updated ${app.name} (${app.id})`));
|
|
1655
1747
|
console.log(` Mode: ${app.mode ?? "container"}`);
|
|
1656
1748
|
console.log(` Access: ${describeAccess(app)}`);
|
|
1657
1749
|
console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
|
|
@@ -1660,7 +1752,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1660
1752
|
);
|
|
1661
1753
|
if (app.restartRequired) {
|
|
1662
1754
|
console.log(
|
|
1663
|
-
|
|
1755
|
+
chalk12.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
|
|
1664
1756
|
);
|
|
1665
1757
|
}
|
|
1666
1758
|
}
|
|
@@ -1668,37 +1760,42 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
|
|
|
1668
1760
|
function intOption(flag, raw) {
|
|
1669
1761
|
const n = Number(raw);
|
|
1670
1762
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1671
|
-
console.error(
|
|
1763
|
+
console.error(chalk12.red(`${flag} expects a positive whole number, got "${raw}".`));
|
|
1672
1764
|
process.exit(1);
|
|
1673
1765
|
}
|
|
1674
1766
|
return n;
|
|
1675
1767
|
}
|
|
1676
|
-
appsCommand.command("delete <id>").description("Delete an app and all its resources").action(async (id) => {
|
|
1768
|
+
appsCommand.command("delete <id>").description("Delete an app and all its resources").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
|
|
1677
1769
|
const apiKey = getApiKey();
|
|
1678
1770
|
requireApiKey(apiKey);
|
|
1679
|
-
|
|
1680
|
-
{
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1771
|
+
if (!opts.yes) {
|
|
1772
|
+
const { confirm } = await prompt(
|
|
1773
|
+
[
|
|
1774
|
+
{
|
|
1775
|
+
type: "confirm",
|
|
1776
|
+
name: "confirm",
|
|
1777
|
+
message: `Delete app ${id}? This stops the app and destroys its database, secrets and snapshots.`,
|
|
1778
|
+
default: false
|
|
1779
|
+
}
|
|
1780
|
+
],
|
|
1781
|
+
"Confirm with a flag: videan apps delete <id> --yes"
|
|
1782
|
+
);
|
|
1783
|
+
if (!confirm) return;
|
|
1784
|
+
}
|
|
1688
1785
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1689
1786
|
const { warnings } = await client4.apps.delete(id);
|
|
1690
|
-
console.log(
|
|
1787
|
+
console.log(chalk12.green("App deleted."));
|
|
1691
1788
|
if (warnings.length > 0) {
|
|
1692
|
-
console.log(
|
|
1789
|
+
console.log(chalk12.yellow("\nSome resources could not be released:"));
|
|
1693
1790
|
for (const w of warnings) {
|
|
1694
|
-
console.log(
|
|
1791
|
+
console.log(chalk12.yellow(` ${w.step}: ${w.error || "unknown error"}`));
|
|
1695
1792
|
}
|
|
1696
1793
|
}
|
|
1697
1794
|
});
|
|
1698
1795
|
|
|
1699
1796
|
// src/commands/status.ts
|
|
1700
1797
|
import { Command as Command11 } from "commander";
|
|
1701
|
-
import
|
|
1798
|
+
import chalk13 from "chalk";
|
|
1702
1799
|
function accessLine(app) {
|
|
1703
1800
|
const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
|
|
1704
1801
|
return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
|
|
@@ -1708,42 +1805,41 @@ var statusCommand = new Command11("status").description("Show current app status
|
|
|
1708
1805
|
requireApiKey(apiKey);
|
|
1709
1806
|
const appId = opts.app || getCurrentApp();
|
|
1710
1807
|
if (!appId) {
|
|
1711
|
-
console.error(
|
|
1808
|
+
console.error(chalk13.red("No app specified. Use --app or link an app."));
|
|
1712
1809
|
process.exit(1);
|
|
1713
1810
|
}
|
|
1714
1811
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1715
1812
|
const app = await client4.apps.get(appId);
|
|
1716
1813
|
const health = await client4.apps.health(appId);
|
|
1717
|
-
console.log(
|
|
1814
|
+
console.log(chalk13.bold(app.name));
|
|
1718
1815
|
console.log(` ID: ${app.id}`);
|
|
1719
1816
|
console.log(` Created: ${app.createdAt}`);
|
|
1720
1817
|
console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
|
|
1721
1818
|
console.log(` Access: ${accessLine(app)}`);
|
|
1722
1819
|
console.log(` State: ${formatStatus(health.state)}`);
|
|
1723
|
-
console.log(` Healthy: ${health.healthy ?
|
|
1724
|
-
console.log(` URL: ${
|
|
1820
|
+
console.log(` Healthy: ${health.healthy ? chalk13.green("yes") : chalk13.red("no")}`);
|
|
1821
|
+
console.log(` URL: ${chalk13.cyan(health.url || appUrl(app.slug))}`);
|
|
1725
1822
|
const task = health.task;
|
|
1726
1823
|
if (task) {
|
|
1727
1824
|
if (task.exit_code !== void 0 && task.exit_code !== null) {
|
|
1728
1825
|
const code = String(task.exit_code);
|
|
1729
|
-
console.log(` Exit code: ${task.exit_code === 0 ? code :
|
|
1826
|
+
console.log(` Exit code: ${task.exit_code === 0 ? code : chalk13.red(code)}`);
|
|
1730
1827
|
}
|
|
1731
1828
|
if (task.stopped_reason) {
|
|
1732
|
-
console.log(` Stopped: ${
|
|
1829
|
+
console.log(` Stopped: ${chalk13.yellow(task.stopped_reason)}`);
|
|
1733
1830
|
}
|
|
1734
1831
|
if (task.container_reason) {
|
|
1735
|
-
console.log(` Detail: ${
|
|
1832
|
+
console.log(` Detail: ${chalk13.yellow(task.container_reason)}`);
|
|
1736
1833
|
}
|
|
1737
1834
|
}
|
|
1738
1835
|
if (!health.healthy) {
|
|
1739
|
-
console.log(
|
|
1836
|
+
console.log(chalk13.gray("\nRun `videan logs` to see why."));
|
|
1740
1837
|
}
|
|
1741
1838
|
});
|
|
1742
1839
|
|
|
1743
1840
|
// src/commands/keys.ts
|
|
1744
1841
|
import { Command as Command12 } from "commander";
|
|
1745
|
-
import
|
|
1746
|
-
import inquirer5 from "inquirer";
|
|
1842
|
+
import chalk14 from "chalk";
|
|
1747
1843
|
var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
|
|
1748
1844
|
keysCommand.command("create <name>").description("Create an API key").option("-o, --org <id>", "Organization ID").option("-e, --expires <days>", "Expire the key after N days").option(
|
|
1749
1845
|
"--app <id>",
|
|
@@ -1756,7 +1852,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1756
1852
|
if (opts.expires !== void 0) {
|
|
1757
1853
|
expiresInDays = Number(opts.expires);
|
|
1758
1854
|
if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
|
|
1759
|
-
console.error(
|
|
1855
|
+
console.error(chalk14.red("--expires must be a whole number of days, at least 1."));
|
|
1760
1856
|
process.exit(1);
|
|
1761
1857
|
}
|
|
1762
1858
|
}
|
|
@@ -1767,22 +1863,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
|
|
|
1767
1863
|
expiresInDays,
|
|
1768
1864
|
appId: opts.app
|
|
1769
1865
|
});
|
|
1770
|
-
console.log(
|
|
1866
|
+
console.log(chalk14.green(`Created key "${key.name}" (${key.id})`));
|
|
1771
1867
|
console.log(`
|
|
1772
|
-
${
|
|
1868
|
+
${chalk14.bold(key.key)}
|
|
1773
1869
|
`);
|
|
1774
|
-
console.log(
|
|
1870
|
+
console.log(chalk14.yellow("Copy it now. It cannot be shown again."));
|
|
1775
1871
|
if (key.appId) {
|
|
1776
1872
|
console.log(
|
|
1777
|
-
|
|
1873
|
+
chalk14.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
|
|
1778
1874
|
);
|
|
1779
1875
|
} else {
|
|
1780
1876
|
console.log(
|
|
1781
|
-
|
|
1877
|
+
chalk14.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
|
|
1782
1878
|
);
|
|
1783
1879
|
}
|
|
1784
1880
|
if (key.expiresAt) {
|
|
1785
|
-
console.log(
|
|
1881
|
+
console.log(chalk14.gray(`Expires ${key.expiresAt}`));
|
|
1786
1882
|
}
|
|
1787
1883
|
});
|
|
1788
1884
|
keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
@@ -1792,13 +1888,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1792
1888
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1793
1889
|
const keys = await client4.apiKeys.list(orgId);
|
|
1794
1890
|
if (keys.length === 0) {
|
|
1795
|
-
console.log(
|
|
1891
|
+
console.log(chalk14.gray("No API keys. Create one with `videan keys create <name>`."));
|
|
1796
1892
|
return;
|
|
1797
1893
|
}
|
|
1798
1894
|
const widths = [24, 24, 16, 22, 12, 24];
|
|
1799
1895
|
console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
|
|
1800
1896
|
for (const key of keys) {
|
|
1801
|
-
const state = key.revokedAt ?
|
|
1897
|
+
const state = key.revokedAt ? chalk14.red("revoked") : key.expiresAt && new Date(key.expiresAt) < /* @__PURE__ */ new Date() ? chalk14.yellow("expired") : chalk14.green("active");
|
|
1802
1898
|
console.log(formatRow(
|
|
1803
1899
|
[
|
|
1804
1900
|
key.name,
|
|
@@ -1812,21 +1908,24 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
|
|
|
1812
1908
|
));
|
|
1813
1909
|
}
|
|
1814
1910
|
});
|
|
1815
|
-
keysCommand.command("revoke <id>").description("Revoke an API key").action(async (id) => {
|
|
1911
|
+
keysCommand.command("revoke <id>").description("Revoke an API key").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
|
|
1816
1912
|
const apiKey = getApiKey();
|
|
1817
1913
|
requireApiKey(apiKey);
|
|
1818
|
-
|
|
1819
|
-
{
|
|
1820
|
-
|
|
1821
|
-
|
|
1914
|
+
if (!opts.yes) {
|
|
1915
|
+
const { confirm } = await prompt(
|
|
1916
|
+
[{ type: "confirm", name: "confirm", message: `Revoke key ${id}?`, default: false }],
|
|
1917
|
+
"Confirm with a flag: videan keys revoke <id> --yes"
|
|
1918
|
+
);
|
|
1919
|
+
if (!confirm) return;
|
|
1920
|
+
}
|
|
1822
1921
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1823
1922
|
await client4.apiKeys.revoke(id);
|
|
1824
|
-
console.log(
|
|
1923
|
+
console.log(chalk14.green("Key revoked."));
|
|
1825
1924
|
});
|
|
1826
1925
|
|
|
1827
1926
|
// src/commands/orgs.ts
|
|
1828
1927
|
import { Command as Command13 } from "commander";
|
|
1829
|
-
import
|
|
1928
|
+
import chalk15 from "chalk";
|
|
1830
1929
|
var orgsCommand = new Command13("orgs").description("Manage organizations");
|
|
1831
1930
|
orgsCommand.command("list").description("List your organizations").action(async () => {
|
|
1832
1931
|
const apiKey = getApiKey();
|
|
@@ -1835,14 +1934,14 @@ orgsCommand.command("list").description("List your organizations").action(async
|
|
|
1835
1934
|
const orgs = await client4.orgs.list();
|
|
1836
1935
|
const current = getDefaultOrg();
|
|
1837
1936
|
if (orgs.length === 0) {
|
|
1838
|
-
console.log(
|
|
1937
|
+
console.log(chalk15.gray("No organizations. Create one with `videan orgs create <name>`."));
|
|
1839
1938
|
return;
|
|
1840
1939
|
}
|
|
1841
1940
|
const widths = [24, 24, 10, 8];
|
|
1842
1941
|
console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
|
|
1843
1942
|
for (const org of orgs) {
|
|
1844
1943
|
console.log(formatRow(
|
|
1845
|
-
[org.name, org.id, org.plan, org.id === current ?
|
|
1944
|
+
[org.name, org.id, org.plan, org.id === current ? chalk15.green("default") : ""],
|
|
1846
1945
|
widths
|
|
1847
1946
|
));
|
|
1848
1947
|
}
|
|
@@ -1853,7 +1952,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
|
|
|
1853
1952
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1854
1953
|
const org = await client4.orgs.create(name);
|
|
1855
1954
|
if (!getDefaultOrg()) setDefaultOrg(org.id);
|
|
1856
|
-
console.log(
|
|
1955
|
+
console.log(chalk15.green(`Organization "${org.name}" created (${org.id})`));
|
|
1857
1956
|
});
|
|
1858
1957
|
orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
|
|
1859
1958
|
const apiKey = getApiKey();
|
|
@@ -1861,7 +1960,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
|
|
|
1861
1960
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1862
1961
|
const org = await client4.orgs.get(id);
|
|
1863
1962
|
setDefaultOrg(org.id);
|
|
1864
|
-
console.log(
|
|
1963
|
+
console.log(chalk15.green(`Default organization set to "${org.name}".`));
|
|
1865
1964
|
});
|
|
1866
1965
|
orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
|
|
1867
1966
|
const apiKey = getApiKey();
|
|
@@ -1884,12 +1983,12 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1884
1983
|
const orgId = requireOrg(opts.org || getDefaultOrg());
|
|
1885
1984
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1886
1985
|
await client4.orgs.invite(orgId, email);
|
|
1887
|
-
console.log(
|
|
1986
|
+
console.log(chalk15.green(`Invited ${email}.`));
|
|
1888
1987
|
});
|
|
1889
1988
|
|
|
1890
1989
|
// src/commands/storage.ts
|
|
1891
1990
|
import { Command as Command14 } from "commander";
|
|
1892
|
-
import
|
|
1991
|
+
import chalk16 from "chalk";
|
|
1893
1992
|
import fs5 from "fs-extra";
|
|
1894
1993
|
import path5 from "node:path";
|
|
1895
1994
|
var storageCommand = new Command14("storage").description("Store files for an app");
|
|
@@ -1901,7 +2000,7 @@ function client() {
|
|
|
1901
2000
|
function requireApp(opt) {
|
|
1902
2001
|
const appId = opt || getCurrentApp();
|
|
1903
2002
|
if (!appId) {
|
|
1904
|
-
console.error(
|
|
2003
|
+
console.error(chalk16.red("No app specified. Use --app or link an app."));
|
|
1905
2004
|
process.exit(1);
|
|
1906
2005
|
}
|
|
1907
2006
|
return appId;
|
|
@@ -1924,7 +2023,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1924
2023
|
return;
|
|
1925
2024
|
}
|
|
1926
2025
|
if (objects.length === 0) {
|
|
1927
|
-
console.log(
|
|
2026
|
+
console.log(chalk16.gray("No objects stored. Upload one with `videan storage put <file>`."));
|
|
1928
2027
|
return;
|
|
1929
2028
|
}
|
|
1930
2029
|
const widths = [52, 12, 24];
|
|
@@ -1933,13 +2032,13 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
|
|
|
1933
2032
|
console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
|
|
1934
2033
|
}
|
|
1935
2034
|
const total = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1936
|
-
console.log(
|
|
2035
|
+
console.log(chalk16.dim(`
|
|
1937
2036
|
${objects.length} object(s), ${human(total)}`));
|
|
1938
2037
|
});
|
|
1939
2038
|
storageCommand.command("put <file> [key]").description("Upload a file (key defaults to the file's name)").option("-a, --app <id>", "App ID").option("-t, --type <mime>", "Content type").action(async (file, key, opts) => {
|
|
1940
2039
|
const appId = requireApp(opts.app);
|
|
1941
2040
|
if (!await fs5.pathExists(file)) {
|
|
1942
|
-
console.error(
|
|
2041
|
+
console.error(chalk16.red(`No such file: ${file}`));
|
|
1943
2042
|
process.exit(1);
|
|
1944
2043
|
}
|
|
1945
2044
|
const body = await fs5.readFile(file);
|
|
@@ -1948,27 +2047,27 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
|
|
|
1948
2047
|
contentType: opts.type
|
|
1949
2048
|
});
|
|
1950
2049
|
console.log(
|
|
1951
|
-
|
|
2050
|
+
chalk16.green(`Uploaded ${objectKey}`) + chalk16.dim(` (${human(body.length)})`)
|
|
1952
2051
|
);
|
|
1953
|
-
if (stored.etag) console.log(
|
|
2052
|
+
if (stored.etag) console.log(chalk16.dim(`etag ${stored.etag}`));
|
|
1954
2053
|
});
|
|
1955
2054
|
storageCommand.command("get <key> [file]").description("Download an object (defaults to the key's basename)").option("-a, --app <id>", "App ID").action(async (key, file, opts) => {
|
|
1956
2055
|
const appId = requireApp(opts.app);
|
|
1957
2056
|
const bytes = await client().storage.download(appId, key);
|
|
1958
2057
|
const dest = file || path5.basename(key);
|
|
1959
2058
|
await fs5.writeFile(dest, bytes);
|
|
1960
|
-
console.log(
|
|
2059
|
+
console.log(chalk16.green(`Wrote ${dest}`) + chalk16.dim(` (${human(bytes.length)})`));
|
|
1961
2060
|
});
|
|
1962
2061
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
1963
2062
|
const appId = requireApp(opts.app);
|
|
1964
2063
|
await client().storage.delete(appId, key);
|
|
1965
|
-
console.log(
|
|
2064
|
+
console.log(chalk16.green(`Deleted ${key}`));
|
|
1966
2065
|
});
|
|
1967
2066
|
storageCommand.command("url <key>").description("Print a temporary link to an object").option("-a, --app <id>", "App ID").option("-e, --expires <seconds>", "Lifetime in seconds (30-3600)", "900").option("--upload", "Sign an upload (PUT) URL instead of a download").option("-d, --download <filename>", "Serve as a download with this filename").action(async (key, opts) => {
|
|
1968
2067
|
const appId = requireApp(opts.app);
|
|
1969
2068
|
const expiresIn = Number(opts.expires);
|
|
1970
2069
|
if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
|
|
1971
|
-
console.error(
|
|
2070
|
+
console.error(chalk16.red("--expires must be a whole number of seconds between 30 and 3600."));
|
|
1972
2071
|
process.exit(1);
|
|
1973
2072
|
}
|
|
1974
2073
|
const signed = await client().storage.presign(appId, key, {
|
|
@@ -1978,7 +2077,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
|
|
|
1978
2077
|
});
|
|
1979
2078
|
console.log(signed.url);
|
|
1980
2079
|
console.error(
|
|
1981
|
-
|
|
2080
|
+
chalk16.yellow(
|
|
1982
2081
|
`This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
|
|
1983
2082
|
)
|
|
1984
2083
|
);
|
|
@@ -1991,7 +2090,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1991
2090
|
|
|
1992
2091
|
// src/commands/email.ts
|
|
1993
2092
|
import { Command as Command15 } from "commander";
|
|
1994
|
-
import
|
|
2093
|
+
import chalk17 from "chalk";
|
|
1995
2094
|
import fs6 from "fs-extra";
|
|
1996
2095
|
var emailCommand = new Command15("email").description("Send email from an app");
|
|
1997
2096
|
function client2() {
|
|
@@ -2002,7 +2101,7 @@ function client2() {
|
|
|
2002
2101
|
function requireApp2(opt) {
|
|
2003
2102
|
const appId = opt || getCurrentApp();
|
|
2004
2103
|
if (!appId) {
|
|
2005
|
-
console.error(
|
|
2104
|
+
console.error(chalk17.red("No app specified. Use --app or link an app."));
|
|
2006
2105
|
process.exit(1);
|
|
2007
2106
|
}
|
|
2008
2107
|
return appId;
|
|
@@ -2018,7 +2117,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
2018
2117
|
else text = contents;
|
|
2019
2118
|
}
|
|
2020
2119
|
if (!text && !html) {
|
|
2021
|
-
console.error(
|
|
2120
|
+
console.error(chalk17.red("Nothing to send. Pass --text, --html or --body-file."));
|
|
2022
2121
|
process.exit(1);
|
|
2023
2122
|
}
|
|
2024
2123
|
const sent = await client2().email.send(appId, {
|
|
@@ -2032,12 +2131,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
2032
2131
|
console.log(JSON.stringify(sent, null, 2));
|
|
2033
2132
|
return;
|
|
2034
2133
|
}
|
|
2035
|
-
console.log(
|
|
2134
|
+
console.log(chalk17.green("Sent"));
|
|
2036
2135
|
console.log(` From: ${sent.from}`);
|
|
2037
2136
|
console.log(` To: ${sent.to.join(", ")}`);
|
|
2038
2137
|
console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
|
|
2039
2138
|
console.log(
|
|
2040
|
-
|
|
2139
|
+
chalk17.dim(
|
|
2041
2140
|
` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
|
|
2042
2141
|
)
|
|
2043
2142
|
);
|
|
@@ -2052,12 +2151,12 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
2052
2151
|
console.log(`Sent today: ${quota.sent}`);
|
|
2053
2152
|
console.log(`Daily quota: ${quota.quota}`);
|
|
2054
2153
|
console.log(`Remaining: ${quota.remaining}`);
|
|
2055
|
-
console.log(
|
|
2154
|
+
console.log(chalk17.dim(`Resets at: ${quota.resetsAt}`));
|
|
2056
2155
|
});
|
|
2057
2156
|
|
|
2058
2157
|
// src/commands/jobs.ts
|
|
2059
2158
|
import { Command as Command16 } from "commander";
|
|
2060
|
-
import
|
|
2159
|
+
import chalk18 from "chalk";
|
|
2061
2160
|
import fs7 from "fs-extra";
|
|
2062
2161
|
var jobsCommand = new Command16("jobs").description("Enqueue and inspect background jobs");
|
|
2063
2162
|
function client3() {
|
|
@@ -2068,7 +2167,7 @@ function client3() {
|
|
|
2068
2167
|
function requireApp3(opt) {
|
|
2069
2168
|
const appId = opt || getCurrentApp();
|
|
2070
2169
|
if (!appId) {
|
|
2071
|
-
console.error(
|
|
2170
|
+
console.error(chalk18.red("No app specified. Use --app or run `videan init` first."));
|
|
2072
2171
|
process.exit(1);
|
|
2073
2172
|
}
|
|
2074
2173
|
return appId;
|
|
@@ -2076,20 +2175,20 @@ function requireApp3(opt) {
|
|
|
2076
2175
|
function intOption2(flag, raw) {
|
|
2077
2176
|
const n = Number(raw);
|
|
2078
2177
|
if (!Number.isInteger(n) || n < 0) {
|
|
2079
|
-
console.error(
|
|
2178
|
+
console.error(chalk18.red(`${flag} expects a whole number, got "${raw}".`));
|
|
2080
2179
|
process.exit(1);
|
|
2081
2180
|
}
|
|
2082
2181
|
return n;
|
|
2083
2182
|
}
|
|
2084
2183
|
var STATUS_COLOR = {
|
|
2085
|
-
queued:
|
|
2086
|
-
running:
|
|
2087
|
-
succeeded:
|
|
2088
|
-
dead:
|
|
2089
|
-
cancelled:
|
|
2184
|
+
queued: chalk18.yellow,
|
|
2185
|
+
running: chalk18.cyan,
|
|
2186
|
+
succeeded: chalk18.green,
|
|
2187
|
+
dead: chalk18.red,
|
|
2188
|
+
cancelled: chalk18.gray
|
|
2090
2189
|
};
|
|
2091
2190
|
function statusLabel(status) {
|
|
2092
|
-
return (STATUS_COLOR[status] ??
|
|
2191
|
+
return (STATUS_COLOR[status] ?? chalk18.white)(status);
|
|
2093
2192
|
}
|
|
2094
2193
|
jobsCommand.command("enqueue").description("Queue a job for the app to run").option("-a, --app <id>", "App ID").option("-q, --queue <name>", "Queue name (default: default)").option("-p, --path <path>", "Path on the app to POST to (default: /jobs)").option("-d, --data <json>", "JSON payload").option("-f, --file <path>", "Read the JSON payload from a file").option("--delay <seconds>", "Run no earlier than this many seconds from now").option("--attempts <n>", "Attempts before the job is given up on (default: 3)").option("--timeout <seconds>", "How long the handler gets per attempt (default: 60)").option("--key <key>", "Idempotency key -- a repeat returns the same job").option("--json", "Print the raw response").action(async (opts) => {
|
|
2095
2194
|
const appId = requireApp3(opts.app);
|
|
@@ -2099,7 +2198,7 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2099
2198
|
try {
|
|
2100
2199
|
payload = JSON.parse(raw);
|
|
2101
2200
|
} catch (e) {
|
|
2102
|
-
console.error(
|
|
2201
|
+
console.error(chalk18.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
|
|
2103
2202
|
process.exit(1);
|
|
2104
2203
|
}
|
|
2105
2204
|
}
|
|
@@ -2117,13 +2216,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
|
|
|
2117
2216
|
return;
|
|
2118
2217
|
}
|
|
2119
2218
|
if (job.deduplicated) {
|
|
2120
|
-
console.log(
|
|
2219
|
+
console.log(chalk18.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
|
|
2121
2220
|
} else {
|
|
2122
|
-
console.log(
|
|
2221
|
+
console.log(chalk18.green(`Queued ${job.id}`));
|
|
2123
2222
|
}
|
|
2124
|
-
console.log(
|
|
2223
|
+
console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2125
2224
|
if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
|
|
2126
|
-
console.log(
|
|
2225
|
+
console.log(chalk18.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
|
|
2127
2226
|
}
|
|
2128
2227
|
});
|
|
2129
2228
|
jobsCommand.command("list").description("List jobs, newest first").option("-a, --app <id>", "App ID").option("-s, --status <status>", "queued, running, succeeded, dead or cancelled").option("-q, --queue <name>", "Only this queue").option("-n, --limit <n>", "How many to show (default: 20)").option("--json", "Print the raw response").action(async (opts) => {
|
|
@@ -2138,17 +2237,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
|
|
|
2138
2237
|
return;
|
|
2139
2238
|
}
|
|
2140
2239
|
const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
|
|
2141
|
-
console.log(depth ||
|
|
2240
|
+
console.log(depth || chalk18.gray("No jobs yet."));
|
|
2142
2241
|
if (jobs.length === 0) return;
|
|
2143
2242
|
console.log();
|
|
2144
2243
|
for (const job of jobs) {
|
|
2145
|
-
const attempts = job.attempts > 0 ?
|
|
2244
|
+
const attempts = job.attempts > 0 ? chalk18.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
|
|
2146
2245
|
console.log(
|
|
2147
|
-
`${statusLabel(job.status).padEnd(20)} ${
|
|
2246
|
+
`${statusLabel(job.status).padEnd(20)} ${chalk18.white(job.id)} ${chalk18.gray(
|
|
2148
2247
|
`${job.queue} ${job.path}`
|
|
2149
2248
|
)}${attempts}`
|
|
2150
2249
|
);
|
|
2151
|
-
if (job.error) console.log(
|
|
2250
|
+
if (job.error) console.log(chalk18.red(` ${job.error}`));
|
|
2152
2251
|
}
|
|
2153
2252
|
});
|
|
2154
2253
|
jobsCommand.command("get <job-id>").description("Show one job in full").option("-a, --app <id>", "App ID").option("--json", "Print the raw response").action(async (jobId, opts) => {
|
|
@@ -2157,17 +2256,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2157
2256
|
console.log(JSON.stringify(job, null, 2));
|
|
2158
2257
|
return;
|
|
2159
2258
|
}
|
|
2160
|
-
console.log(`${
|
|
2161
|
-
console.log(
|
|
2162
|
-
console.log(
|
|
2163
|
-
if (job.statusCode) console.log(
|
|
2164
|
-
if (job.durationMs) console.log(
|
|
2165
|
-
if (job.error) console.log(
|
|
2259
|
+
console.log(`${chalk18.white(job.id)} ${statusLabel(job.status)}`);
|
|
2260
|
+
console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
|
|
2261
|
+
console.log(chalk18.gray(` Attempts: ${job.attempts}/${job.maxAttempts}`));
|
|
2262
|
+
if (job.statusCode) console.log(chalk18.gray(` Last response: HTTP ${job.statusCode}`));
|
|
2263
|
+
if (job.durationMs) console.log(chalk18.gray(` Took: ${job.durationMs}ms`));
|
|
2264
|
+
if (job.error) console.log(chalk18.red(` Error: ${job.error}`));
|
|
2166
2265
|
if (job.status === "queued" && job.attempts > 0) {
|
|
2167
|
-
console.log(
|
|
2266
|
+
console.log(chalk18.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
|
|
2168
2267
|
}
|
|
2169
2268
|
if (job.payload !== null && job.payload !== void 0) {
|
|
2170
|
-
console.log(
|
|
2269
|
+
console.log(chalk18.gray(" Payload:"));
|
|
2171
2270
|
console.log(
|
|
2172
2271
|
JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
|
|
2173
2272
|
);
|
|
@@ -2175,16 +2274,16 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
|
|
|
2175
2274
|
});
|
|
2176
2275
|
jobsCommand.command("retry <job-id>").description("Run a dead job again, from a clean attempt count").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
|
|
2177
2276
|
const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
|
|
2178
|
-
console.log(
|
|
2277
|
+
console.log(chalk18.green(`Requeued ${job.id}`));
|
|
2179
2278
|
});
|
|
2180
2279
|
jobsCommand.command("cancel <job-id>").description("Cancel a job that has not started yet").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
|
|
2181
2280
|
const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
|
|
2182
|
-
console.log(
|
|
2281
|
+
console.log(chalk18.green(`Cancelled ${job.id}`));
|
|
2183
2282
|
});
|
|
2184
2283
|
|
|
2185
2284
|
// src/commands/usage.ts
|
|
2186
2285
|
import { Command as Command17 } from "commander";
|
|
2187
|
-
import
|
|
2286
|
+
import chalk19 from "chalk";
|
|
2188
2287
|
var usageCommand = new Command17("usage").description("Metered usage and cost for the current month").option("-o, --org <id>", "Organization ID").option("-a, --app <id>", "Show one app's share instead of the whole org").option("-p, --period <YYYY-MM>", "Month to report on (default: the month in progress)").option("--json", "Print the raw response").action(async (opts) => {
|
|
2189
2288
|
const apiKey = getApiKey();
|
|
2190
2289
|
requireApiKey(apiKey);
|
|
@@ -2193,61 +2292,61 @@ var usageCommand = new Command17("usage").description("Metered usage and cost fo
|
|
|
2193
2292
|
const appId = opts.app || getCurrentApp();
|
|
2194
2293
|
const report2 = await client4.apps.usage(appId, opts.period);
|
|
2195
2294
|
if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
|
|
2196
|
-
console.log(
|
|
2295
|
+
console.log(chalk19.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
|
|
2197
2296
|
console.log();
|
|
2198
2297
|
printMetrics(report2.metrics);
|
|
2199
2298
|
console.log();
|
|
2200
2299
|
for (const line of report2.lines) {
|
|
2201
|
-
console.log(` ${
|
|
2300
|
+
console.log(` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)}`);
|
|
2202
2301
|
}
|
|
2203
|
-
console.log(` ${
|
|
2302
|
+
console.log(` ${chalk19.bold("Share of the bill".padEnd(22))} ${chalk19.bold(report2.cost)}`);
|
|
2204
2303
|
console.log();
|
|
2205
2304
|
console.log(
|
|
2206
|
-
|
|
2305
|
+
chalk19.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
|
|
2207
2306
|
);
|
|
2208
2307
|
return;
|
|
2209
2308
|
}
|
|
2210
2309
|
const orgId = opts.org || getDefaultOrg();
|
|
2211
2310
|
if (!orgId) {
|
|
2212
|
-
console.error(
|
|
2311
|
+
console.error(chalk19.red("No org specified. Use --org, or --app for a single app."));
|
|
2213
2312
|
process.exit(1);
|
|
2214
2313
|
}
|
|
2215
2314
|
const report = await client4.orgs.usage(orgId, opts.period);
|
|
2216
2315
|
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
2217
2316
|
const inv = report.invoice;
|
|
2218
2317
|
console.log(
|
|
2219
|
-
|
|
2318
|
+
chalk19.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
|
|
2220
2319
|
);
|
|
2221
2320
|
console.log();
|
|
2222
2321
|
printMetrics(report.metrics);
|
|
2223
2322
|
if (report.apps.length > 0) {
|
|
2224
2323
|
console.log();
|
|
2225
|
-
console.log(
|
|
2324
|
+
console.log(chalk19.bold("By app"));
|
|
2226
2325
|
for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
|
|
2227
2326
|
const name = app.slug || app.name || app.appId || "(deleted apps)";
|
|
2228
|
-
console.log(` ${
|
|
2327
|
+
console.log(` ${chalk19.dim(String(name).padEnd(24))} ${money(app.cents)}`);
|
|
2229
2328
|
}
|
|
2230
2329
|
}
|
|
2231
2330
|
console.log();
|
|
2232
2331
|
for (const line of inv.lines) {
|
|
2233
2332
|
console.log(
|
|
2234
|
-
` ${
|
|
2333
|
+
` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk19.dim(
|
|
2235
2334
|
`(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
|
|
2236
2335
|
)}`
|
|
2237
2336
|
);
|
|
2238
2337
|
}
|
|
2239
|
-
console.log(` ${
|
|
2338
|
+
console.log(` ${chalk19.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
|
|
2240
2339
|
if (inv.includedUsageCents > 0) {
|
|
2241
2340
|
console.log(
|
|
2242
|
-
` ${
|
|
2341
|
+
` ${chalk19.dim("Included".padEnd(22))} ${chalk19.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
|
|
2243
2342
|
);
|
|
2244
2343
|
}
|
|
2245
2344
|
if (inv.baseCents > 0) {
|
|
2246
|
-
console.log(` ${
|
|
2345
|
+
console.log(` ${chalk19.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
|
|
2247
2346
|
}
|
|
2248
|
-
console.log(` ${
|
|
2347
|
+
console.log(` ${chalk19.bold("Total".padEnd(22))} ${chalk19.bold(report.total)}`);
|
|
2249
2348
|
console.log();
|
|
2250
|
-
console.log(
|
|
2349
|
+
console.log(chalk19.dim("Usage to date. The period is priced as it stands right now."));
|
|
2251
2350
|
});
|
|
2252
2351
|
usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
|
|
2253
2352
|
const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
|
|
@@ -2255,11 +2354,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2255
2354
|
if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
|
|
2256
2355
|
for (const plan of plans) {
|
|
2257
2356
|
console.log(
|
|
2258
|
-
`${
|
|
2357
|
+
`${chalk19.bold(plan.name)} ${chalk19.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk19.dim(`, ${plan.includedUsage} usage included`) : "")
|
|
2259
2358
|
);
|
|
2260
2359
|
const l = plan.limits;
|
|
2261
2360
|
console.log(
|
|
2262
|
-
|
|
2361
|
+
chalk19.dim(
|
|
2263
2362
|
` ${l.apps} apps \xB7 ${l.instancesPerApp} instances/app \xB7 up to ${l.maxCpu} CPU / ${l.maxMemory} MB \xB7 ${l.maxStorageMb} MB storage${l.postgres ? " \xB7 postgres" : ""}${l.customDomains ? " \xB7 custom domains" : ""}`
|
|
2264
2363
|
)
|
|
2265
2364
|
);
|
|
@@ -2268,11 +2367,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
|
|
|
2268
2367
|
});
|
|
2269
2368
|
function printMetrics(metrics) {
|
|
2270
2369
|
if (metrics.length === 0) {
|
|
2271
|
-
console.log(
|
|
2370
|
+
console.log(chalk19.dim(" Nothing metered in this period yet."));
|
|
2272
2371
|
return;
|
|
2273
2372
|
}
|
|
2274
2373
|
for (const m of metrics) {
|
|
2275
|
-
console.log(` ${
|
|
2374
|
+
console.log(` ${chalk19.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
|
|
2276
2375
|
}
|
|
2277
2376
|
}
|
|
2278
2377
|
function round(n) {
|
|
@@ -2287,8 +2386,9 @@ function period(start, end) {
|
|
|
2287
2386
|
}
|
|
2288
2387
|
|
|
2289
2388
|
// src/index.ts
|
|
2389
|
+
installPromptCrashGuard();
|
|
2290
2390
|
var program = new Command18();
|
|
2291
|
-
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.
|
|
2391
|
+
program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.2");
|
|
2292
2392
|
program.addCommand(loginCommand);
|
|
2293
2393
|
program.addCommand(logoutCommand);
|
|
2294
2394
|
program.addCommand(initCommand);
|