@videan/cli 0.4.1 → 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 +170 -122
- 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
|
|
|
@@ -834,12 +834,94 @@ function clearConfig() {
|
|
|
834
834
|
config.clear();
|
|
835
835
|
}
|
|
836
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
|
+
|
|
837
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
|
+
}
|
|
838
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) => {
|
|
839
921
|
const password = opts.password || process.env.VIDEAN_PASSWORD || process.env.CLOUD_PASSWORD;
|
|
840
922
|
if ((!opts.email || !password) && !process.stdin.isTTY) {
|
|
841
923
|
console.error(
|
|
842
|
-
|
|
924
|
+
chalk2.red(
|
|
843
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."
|
|
844
926
|
)
|
|
845
927
|
);
|
|
@@ -852,12 +934,13 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
852
934
|
const answers = {
|
|
853
935
|
email: opts.email,
|
|
854
936
|
password,
|
|
855
|
-
...prompts.length ? await
|
|
937
|
+
...prompts.length ? await inquirer2.prompt(prompts) : {}
|
|
856
938
|
};
|
|
857
939
|
if (opts.apiUrl) setBaseUrl(opts.apiUrl.replace(/\/$/, ""));
|
|
858
940
|
const baseUrl = (getBaseUrl() || DEFAULT_API_URL2).replace(/\/$/, "");
|
|
859
941
|
const spinner = ora("Logging in...").start();
|
|
860
942
|
let token;
|
|
943
|
+
let login;
|
|
861
944
|
try {
|
|
862
945
|
const response = await fetch(`${baseUrl}/auth/login`, {
|
|
863
946
|
method: "POST",
|
|
@@ -870,17 +953,22 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
870
953
|
} else {
|
|
871
954
|
spinner.fail(`Login failed: ${response.status} from ${baseUrl}/auth/login`);
|
|
872
955
|
console.error(
|
|
873
|
-
|
|
956
|
+
chalk2.gray(" Wrong API URL? Re-run with --api-url <url>, or `videan logout` to reset.")
|
|
874
957
|
);
|
|
875
958
|
}
|
|
876
959
|
process.exit(1);
|
|
877
960
|
}
|
|
878
961
|
const data = await response.json();
|
|
879
|
-
|
|
962
|
+
login = data.data;
|
|
880
963
|
} catch (e) {
|
|
881
964
|
spinner.fail(`Login failed: could not reach ${baseUrl} (${e?.message || e})`);
|
|
882
965
|
process.exit(1);
|
|
883
966
|
}
|
|
967
|
+
if (login.requires_2fa) {
|
|
968
|
+
token = await completeTwoFactor(spinner, baseUrl, login);
|
|
969
|
+
} else {
|
|
970
|
+
token = login.token;
|
|
971
|
+
}
|
|
884
972
|
const session = new CloudClient({ apiKey: token, baseUrl });
|
|
885
973
|
let orgId;
|
|
886
974
|
try {
|
|
@@ -899,7 +987,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
899
987
|
process.exit(1);
|
|
900
988
|
} else if (orgs.length > 1) {
|
|
901
989
|
spinner.stop();
|
|
902
|
-
const pick = await
|
|
990
|
+
const pick = await inquirer2.prompt([
|
|
903
991
|
{
|
|
904
992
|
type: "list",
|
|
905
993
|
name: "orgId",
|
|
@@ -919,7 +1007,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
919
1007
|
if (!orgId) {
|
|
920
1008
|
setApiKey(token);
|
|
921
1009
|
spinner.warn(
|
|
922
|
-
|
|
1010
|
+
chalk2.yellow("Logged in, but you have no organization yet. Run `videan orgs create <name>`.")
|
|
923
1011
|
);
|
|
924
1012
|
return;
|
|
925
1013
|
}
|
|
@@ -930,32 +1018,32 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
|
|
|
930
1018
|
});
|
|
931
1019
|
setApiKey(key.key);
|
|
932
1020
|
setDefaultOrg(orgId);
|
|
933
|
-
spinner.succeed(
|
|
934
|
-
console.log(
|
|
935
|
-
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}\`)`));
|
|
936
1024
|
} catch (e) {
|
|
937
1025
|
setApiKey(token);
|
|
938
1026
|
setDefaultOrg(orgId);
|
|
939
1027
|
spinner.warn(
|
|
940
|
-
|
|
1028
|
+
chalk2.yellow(`Logged in with a temporary session (key creation failed: ${e?.message}).`)
|
|
941
1029
|
);
|
|
942
1030
|
}
|
|
943
1031
|
});
|
|
944
1032
|
|
|
945
1033
|
// src/commands/logout.ts
|
|
946
1034
|
import { Command as Command2 } from "commander";
|
|
947
|
-
import
|
|
1035
|
+
import chalk3 from "chalk";
|
|
948
1036
|
var logoutCommand = new Command2("logout").description("Forget the saved key, org, app and API URL").action(() => {
|
|
949
1037
|
const where = configPath();
|
|
950
1038
|
clearConfig();
|
|
951
|
-
console.log(
|
|
952
|
-
console.log(
|
|
1039
|
+
console.log(chalk3.green("Logged out."));
|
|
1040
|
+
console.log(chalk3.gray(` Cleared ${where}`));
|
|
953
1041
|
});
|
|
954
1042
|
|
|
955
1043
|
// src/commands/init.ts
|
|
956
1044
|
import { Command as Command3 } from "commander";
|
|
957
|
-
import
|
|
958
|
-
import
|
|
1045
|
+
import inquirer3 from "inquirer";
|
|
1046
|
+
import chalk4 from "chalk";
|
|
959
1047
|
import fs2 from "fs-extra";
|
|
960
1048
|
import path2 from "node:path";
|
|
961
1049
|
|
|
@@ -1022,20 +1110,20 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
|
|
|
1022
1110
|
const interactive = process.stdin.isTTY && !opts.yes;
|
|
1023
1111
|
if (await fs2.pathExists(configPath2)) {
|
|
1024
1112
|
if (!interactive) {
|
|
1025
|
-
console.log(
|
|
1113
|
+
console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
|
|
1026
1114
|
} else {
|
|
1027
|
-
console.log(
|
|
1028
|
-
const { overwrite } = await
|
|
1115
|
+
console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
|
|
1116
|
+
const { overwrite } = await inquirer3.prompt([
|
|
1029
1117
|
{ type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
|
|
1030
1118
|
]);
|
|
1031
1119
|
if (!overwrite) {
|
|
1032
|
-
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}`));
|
|
1033
1121
|
return;
|
|
1034
1122
|
}
|
|
1035
1123
|
}
|
|
1036
1124
|
}
|
|
1037
1125
|
if (!await fs2.pathExists(configPath2) || interactive) {
|
|
1038
|
-
const answers = interactive ? await
|
|
1126
|
+
const answers = interactive ? await inquirer3.prompt([
|
|
1039
1127
|
{ type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
|
|
1040
1128
|
{
|
|
1041
1129
|
type: "list",
|
|
@@ -1056,21 +1144,21 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
|
|
|
1056
1144
|
""
|
|
1057
1145
|
].join("\n");
|
|
1058
1146
|
await fs2.writeFile(configPath2, config2, "utf-8");
|
|
1059
|
-
console.log(
|
|
1147
|
+
console.log(chalk4.green(`Created ${CONFIG_FILE_NAME}`));
|
|
1060
1148
|
}
|
|
1061
|
-
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}`));
|
|
1062
1150
|
});
|
|
1063
1151
|
|
|
1064
1152
|
// src/commands/deploy.ts
|
|
1065
1153
|
import { Command as Command4 } from "commander";
|
|
1066
|
-
import
|
|
1154
|
+
import chalk6 from "chalk";
|
|
1067
1155
|
import ora2 from "ora";
|
|
1068
1156
|
|
|
1069
1157
|
// src/util.ts
|
|
1070
1158
|
import fs3 from "fs-extra";
|
|
1071
1159
|
import path3 from "node:path";
|
|
1072
1160
|
import ignore from "ignore";
|
|
1073
|
-
import
|
|
1161
|
+
import chalk5 from "chalk";
|
|
1074
1162
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
1075
1163
|
async function collectFiles(dir) {
|
|
1076
1164
|
const ig = ignore();
|
|
@@ -1131,15 +1219,15 @@ async function collectFiles(dir) {
|
|
|
1131
1219
|
function formatStatus(status) {
|
|
1132
1220
|
switch (status) {
|
|
1133
1221
|
case "running":
|
|
1134
|
-
return
|
|
1222
|
+
return chalk5.green(status);
|
|
1135
1223
|
case "building":
|
|
1136
1224
|
case "deploying":
|
|
1137
|
-
return
|
|
1225
|
+
return chalk5.yellow(status);
|
|
1138
1226
|
case "failed":
|
|
1139
1227
|
case "crashed":
|
|
1140
|
-
return
|
|
1228
|
+
return chalk5.red(status);
|
|
1141
1229
|
case "stopped":
|
|
1142
|
-
return
|
|
1230
|
+
return chalk5.gray(status);
|
|
1143
1231
|
default:
|
|
1144
1232
|
return status;
|
|
1145
1233
|
}
|
|
@@ -1149,13 +1237,13 @@ function formatRow(columns, widths) {
|
|
|
1149
1237
|
}
|
|
1150
1238
|
function requireApiKey(apiKey) {
|
|
1151
1239
|
if (!apiKey) {
|
|
1152
|
-
console.error(
|
|
1240
|
+
console.error(chalk5.red("Not logged in. Run `videan login` first."));
|
|
1153
1241
|
process.exit(1);
|
|
1154
1242
|
}
|
|
1155
1243
|
}
|
|
1156
1244
|
function requireOrg(orgId) {
|
|
1157
1245
|
if (!orgId) {
|
|
1158
|
-
console.error(
|
|
1246
|
+
console.error(chalk5.red("No organization. Pass --org <id> or run `videan login` again."));
|
|
1159
1247
|
process.exit(1);
|
|
1160
1248
|
}
|
|
1161
1249
|
return orgId;
|
|
@@ -1167,7 +1255,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1167
1255
|
requireApiKey(apiKey);
|
|
1168
1256
|
const appId = opts.app || getCurrentApp();
|
|
1169
1257
|
if (!appId) {
|
|
1170
|
-
console.error(
|
|
1258
|
+
console.error(chalk6.red("No app specified. Use --app or run `videan init` first."));
|
|
1171
1259
|
process.exit(1);
|
|
1172
1260
|
}
|
|
1173
1261
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1176,19 +1264,19 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1176
1264
|
const binaryCount = Object.keys(binaryFiles).length;
|
|
1177
1265
|
const fileCount = Object.keys(files).length + binaryCount;
|
|
1178
1266
|
if (!fileCount) {
|
|
1179
|
-
spinner.fail(
|
|
1267
|
+
spinner.fail(chalk6.red(`Nothing to deploy: no files under ${process.cwd()}.`));
|
|
1180
1268
|
process.exit(1);
|
|
1181
1269
|
}
|
|
1182
1270
|
if (skipped.length) {
|
|
1183
|
-
spinner.warn(
|
|
1184
|
-
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}`));
|
|
1185
1273
|
spinner.start();
|
|
1186
1274
|
}
|
|
1187
1275
|
spinner.text = `Pushing ${fileCount} files${binaryCount ? ` (${binaryCount} binary)` : ""}...`;
|
|
1188
1276
|
const snapshot = await client4.apps.push(appId, files, opts.message, binaryFiles);
|
|
1189
1277
|
spinner.text = "Triggering deployment...";
|
|
1190
1278
|
if (opts.env && opts.env !== "preview" && opts.env !== "production") {
|
|
1191
|
-
spinner.fail(
|
|
1279
|
+
spinner.fail(chalk6.red(`--env must be preview or production, not ${opts.env}`));
|
|
1192
1280
|
process.exit(1);
|
|
1193
1281
|
}
|
|
1194
1282
|
const deployment = await client4.apps.deploy(appId, snapshot.id, opts.env);
|
|
@@ -1206,34 +1294,34 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1206
1294
|
} catch (e) {
|
|
1207
1295
|
lastError = e?.message || String(e);
|
|
1208
1296
|
if (++consecutiveErrors >= 5) {
|
|
1209
|
-
spinner.fail(
|
|
1210
|
-
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\`.`));
|
|
1211
1299
|
process.exit(1);
|
|
1212
1300
|
}
|
|
1213
1301
|
}
|
|
1214
1302
|
}
|
|
1215
1303
|
if (current.status === "running") {
|
|
1216
|
-
spinner.succeed(
|
|
1304
|
+
spinner.succeed(chalk6.green(`Deployed successfully!`));
|
|
1217
1305
|
try {
|
|
1218
1306
|
const app = await client4.apps.get(appId);
|
|
1219
1307
|
const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
|
|
1220
1308
|
console.log(
|
|
1221
|
-
gated ?
|
|
1309
|
+
gated ? chalk6.gray(
|
|
1222
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.`
|
|
1223
|
-
) :
|
|
1311
|
+
) : chalk6.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
|
|
1224
1312
|
);
|
|
1225
1313
|
} catch {
|
|
1226
1314
|
}
|
|
1227
1315
|
if (current.url) {
|
|
1228
1316
|
const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
|
|
1229
|
-
console.log(
|
|
1317
|
+
console.log(chalk6.cyan(` URL: ${url}`));
|
|
1230
1318
|
}
|
|
1231
1319
|
if (current.previewUrl) {
|
|
1232
|
-
console.log(
|
|
1320
|
+
console.log(chalk6.gray(` This version: ${current.previewUrl}`));
|
|
1233
1321
|
}
|
|
1234
1322
|
} else if (current.status === "failed" || current.status === "crashed") {
|
|
1235
|
-
spinner.fail(
|
|
1236
|
-
console.log(
|
|
1323
|
+
spinner.fail(chalk6.red(`Deployment ${current.status}: ${current.error || "unknown error"}`));
|
|
1324
|
+
console.log(chalk6.gray(` Logs: videan logs --app ${appId}`));
|
|
1237
1325
|
process.exit(1);
|
|
1238
1326
|
} else {
|
|
1239
1327
|
spinner.info(`Deployment status: ${formatStatus(current.status)}`);
|
|
@@ -1243,7 +1331,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1243
1331
|
|
|
1244
1332
|
// src/commands/pull.ts
|
|
1245
1333
|
import { Command as Command5 } from "commander";
|
|
1246
|
-
import
|
|
1334
|
+
import chalk7 from "chalk";
|
|
1247
1335
|
import ora3 from "ora";
|
|
1248
1336
|
import fs4 from "fs-extra";
|
|
1249
1337
|
import path4 from "node:path";
|
|
@@ -1267,14 +1355,14 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1267
1355
|
if (!snapshotId) {
|
|
1268
1356
|
const appId = opts.app || getCurrentApp();
|
|
1269
1357
|
if (!appId) {
|
|
1270
|
-
spinner.fail(
|
|
1358
|
+
spinner.fail(chalk7.red("No app specified. Use --app, VIDEAN_APP_ID, or name a snapshot id."));
|
|
1271
1359
|
process.exit(1);
|
|
1272
1360
|
}
|
|
1273
1361
|
const [latest] = await get(
|
|
1274
1362
|
`/v1/apps/${appId}/snapshots?limit=1`
|
|
1275
1363
|
);
|
|
1276
1364
|
if (!latest) {
|
|
1277
|
-
spinner.fail(
|
|
1365
|
+
spinner.fail(chalk7.red("This app has no snapshots yet."));
|
|
1278
1366
|
process.exit(1);
|
|
1279
1367
|
}
|
|
1280
1368
|
snapshotId = latest.id;
|
|
@@ -1286,7 +1374,7 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1286
1374
|
await fs4.ensureDir(outDir);
|
|
1287
1375
|
const existing = await fs4.readdir(outDir);
|
|
1288
1376
|
if (existing.length > 0 && !opts.force) {
|
|
1289
|
-
spinner.fail(
|
|
1377
|
+
spinner.fail(chalk7.red(`${outDir} is not empty. Pass --force to write into it anyway.`));
|
|
1290
1378
|
process.exit(1);
|
|
1291
1379
|
}
|
|
1292
1380
|
spinner.text = `Pulling ${detail.manifest.length} files...`;
|
|
@@ -1306,29 +1394,29 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
|
|
|
1306
1394
|
spinner.text = `Pulling ${written}/${detail.manifest.length}...`;
|
|
1307
1395
|
}
|
|
1308
1396
|
spinner.succeed(
|
|
1309
|
-
|
|
1397
|
+
chalk7.green(
|
|
1310
1398
|
`Pulled snapshot ${detail.seq ? `#${detail.seq}` : snapshotId} \u2014 ${written} files into ${outDir}`
|
|
1311
1399
|
)
|
|
1312
1400
|
);
|
|
1313
1401
|
} catch (err) {
|
|
1314
|
-
spinner.fail(
|
|
1402
|
+
spinner.fail(chalk7.red(err instanceof Error ? err.message : String(err)));
|
|
1315
1403
|
process.exit(1);
|
|
1316
1404
|
}
|
|
1317
1405
|
});
|
|
1318
1406
|
|
|
1319
1407
|
// src/commands/logs.ts
|
|
1320
1408
|
import { Command as Command6 } from "commander";
|
|
1321
|
-
import
|
|
1409
|
+
import chalk8 from "chalk";
|
|
1322
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) => {
|
|
1323
1411
|
const apiKey = getApiKey();
|
|
1324
1412
|
requireApiKey(apiKey);
|
|
1325
1413
|
const appId = opts.app || getCurrentApp();
|
|
1326
1414
|
if (!appId) {
|
|
1327
|
-
console.error(
|
|
1415
|
+
console.error(chalk8.red("No app specified. Use --app or link an app."));
|
|
1328
1416
|
process.exit(1);
|
|
1329
1417
|
}
|
|
1330
1418
|
if (opts.search && opts.pattern) {
|
|
1331
|
-
console.error(
|
|
1419
|
+
console.error(chalk8.red("Use either --search or --pattern, not both."));
|
|
1332
1420
|
process.exit(1);
|
|
1333
1421
|
}
|
|
1334
1422
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1351,43 +1439,43 @@ var logsCommand = new Command6("logs").description("View app logs").option("-a,
|
|
|
1351
1439
|
});
|
|
1352
1440
|
for (const entry of page.events) print(entry);
|
|
1353
1441
|
if (page.events.length === 0) {
|
|
1354
|
-
console.log(
|
|
1442
|
+
console.log(chalk8.gray(`No log lines since ${page.start}.`));
|
|
1355
1443
|
}
|
|
1356
1444
|
if (page.truncated) {
|
|
1357
1445
|
console.log(
|
|
1358
|
-
|
|
1446
|
+
chalk8.yellow(
|
|
1359
1447
|
"\nThis is part of the window -- there are more lines than one request reads. Narrow --since or add --search."
|
|
1360
1448
|
)
|
|
1361
1449
|
);
|
|
1362
1450
|
}
|
|
1363
1451
|
});
|
|
1364
1452
|
function print(entry) {
|
|
1365
|
-
console.log(`${
|
|
1453
|
+
console.log(`${chalk8.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
|
|
1366
1454
|
}
|
|
1367
1455
|
function formatLevel(level) {
|
|
1368
1456
|
switch (level) {
|
|
1369
1457
|
case "error":
|
|
1370
|
-
return
|
|
1458
|
+
return chalk8.red("[ERR]");
|
|
1371
1459
|
case "warn":
|
|
1372
|
-
return
|
|
1460
|
+
return chalk8.yellow("[WRN]");
|
|
1373
1461
|
case "info":
|
|
1374
|
-
return
|
|
1462
|
+
return chalk8.blue("[INF]");
|
|
1375
1463
|
case "debug":
|
|
1376
|
-
return
|
|
1464
|
+
return chalk8.gray("[DBG]");
|
|
1377
1465
|
default:
|
|
1378
|
-
return
|
|
1466
|
+
return chalk8.gray("[LOG]");
|
|
1379
1467
|
}
|
|
1380
1468
|
}
|
|
1381
1469
|
|
|
1382
1470
|
// src/commands/metrics.ts
|
|
1383
1471
|
import { Command as Command7 } from "commander";
|
|
1384
|
-
import
|
|
1472
|
+
import chalk9 from "chalk";
|
|
1385
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) => {
|
|
1386
1474
|
const apiKey = getApiKey();
|
|
1387
1475
|
requireApiKey(apiKey);
|
|
1388
1476
|
const appId = opts.app || getCurrentApp();
|
|
1389
1477
|
if (!appId) {
|
|
1390
|
-
console.error(
|
|
1478
|
+
console.error(chalk9.red("No app specified. Use --app or link an app."));
|
|
1391
1479
|
process.exit(1);
|
|
1392
1480
|
}
|
|
1393
1481
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
@@ -1398,7 +1486,7 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
|
|
|
1398
1486
|
}
|
|
1399
1487
|
const t = metrics.totals;
|
|
1400
1488
|
console.log(
|
|
1401
|
-
|
|
1489
|
+
chalk9.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
|
|
1402
1490
|
);
|
|
1403
1491
|
console.log();
|
|
1404
1492
|
console.log(` ${label("Requests")} ${t.requests}`);
|
|
@@ -1406,61 +1494,61 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
|
|
|
1406
1494
|
console.log(` ${label("5xx")} ${count(t.serverErrors, true)}`);
|
|
1407
1495
|
console.log(` ${label("Gate denied")} ${count(t.gateDenied)}`);
|
|
1408
1496
|
console.log(` ${label("Latency avg")} ${ms(t.latencyAvgMs)}`);
|
|
1409
|
-
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${
|
|
1497
|
+
console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk9.dim("(sampled)")}`);
|
|
1410
1498
|
console.log(` ${label("CPU avg")} ${units(t.cpuAvg)}`);
|
|
1411
1499
|
console.log(` ${label("Memory avg")} ${mb(t.memoryAvgMb)}`);
|
|
1412
1500
|
if (t.requests === 0) {
|
|
1413
1501
|
console.log();
|
|
1414
|
-
console.log(
|
|
1502
|
+
console.log(chalk9.dim("No requests recorded in this window."));
|
|
1415
1503
|
}
|
|
1416
1504
|
if (opts.series) {
|
|
1417
1505
|
console.log();
|
|
1418
1506
|
for (const [name, points] of Object.entries(metrics.series)) {
|
|
1419
1507
|
if (points.length === 0) continue;
|
|
1420
|
-
console.log(
|
|
1421
|
-
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}`);
|
|
1422
1510
|
}
|
|
1423
1511
|
}
|
|
1424
1512
|
});
|
|
1425
1513
|
function label(text) {
|
|
1426
|
-
return
|
|
1514
|
+
return chalk9.dim(text.padEnd(13));
|
|
1427
1515
|
}
|
|
1428
1516
|
function count(n, severe = false) {
|
|
1429
|
-
if (n === 0) return
|
|
1430
|
-
return severe ?
|
|
1517
|
+
if (n === 0) return chalk9.dim("0");
|
|
1518
|
+
return severe ? chalk9.red(String(n)) : chalk9.yellow(String(n));
|
|
1431
1519
|
}
|
|
1432
1520
|
function ms(v) {
|
|
1433
|
-
return v === null ?
|
|
1521
|
+
return v === null ? chalk9.dim("\u2014") : `${v} ms`;
|
|
1434
1522
|
}
|
|
1435
1523
|
function mb(v) {
|
|
1436
|
-
return v === null ?
|
|
1524
|
+
return v === null ? chalk9.dim("\u2014") : `${v} MB`;
|
|
1437
1525
|
}
|
|
1438
1526
|
function units(v) {
|
|
1439
|
-
return v === null ?
|
|
1527
|
+
return v === null ? chalk9.dim("\u2014") : `${v} CPU units`;
|
|
1440
1528
|
}
|
|
1441
1529
|
|
|
1442
1530
|
// src/commands/rollback.ts
|
|
1443
1531
|
import { Command as Command8 } from "commander";
|
|
1444
|
-
import
|
|
1532
|
+
import chalk10 from "chalk";
|
|
1445
1533
|
import ora4 from "ora";
|
|
1446
1534
|
var rollbackCommand = new Command8("rollback").description("Rollback to the previous production deployment").option("-a, --app <id>", "App ID").action(async (opts) => {
|
|
1447
1535
|
const apiKey = getApiKey();
|
|
1448
1536
|
requireApiKey(apiKey);
|
|
1449
1537
|
const appId = opts.app || getCurrentApp();
|
|
1450
1538
|
if (!appId) {
|
|
1451
|
-
console.error(
|
|
1539
|
+
console.error(chalk10.red("No app specified. Use --app or link an app."));
|
|
1452
1540
|
process.exit(1);
|
|
1453
1541
|
}
|
|
1454
1542
|
const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1455
1543
|
const spinner = ora4("Rolling back...").start();
|
|
1456
1544
|
try {
|
|
1457
1545
|
const deployment = await client4.apps.rollback(appId);
|
|
1458
|
-
spinner.succeed(
|
|
1546
|
+
spinner.succeed(chalk10.green("Rollback successful!"));
|
|
1459
1547
|
if (deployment.url) {
|
|
1460
|
-
console.log(
|
|
1548
|
+
console.log(chalk10.cyan(` URL: ${deployment.url}`));
|
|
1461
1549
|
}
|
|
1462
1550
|
} catch (error) {
|
|
1463
|
-
spinner.fail(
|
|
1551
|
+
spinner.fail(chalk10.red(`Rollback failed: ${error.message}`));
|
|
1464
1552
|
process.exit(1);
|
|
1465
1553
|
}
|
|
1466
1554
|
});
|
|
@@ -1468,46 +1556,6 @@ var rollbackCommand = new Command8("rollback").description("Rollback to the prev
|
|
|
1468
1556
|
// src/commands/secrets.ts
|
|
1469
1557
|
import { Command as Command9 } from "commander";
|
|
1470
1558
|
import chalk11 from "chalk";
|
|
1471
|
-
|
|
1472
|
-
// src/prompt.ts
|
|
1473
|
-
import inquirer3 from "inquirer";
|
|
1474
|
-
import chalk10 from "chalk";
|
|
1475
|
-
async function prompt(questions, nonInteractiveHint) {
|
|
1476
|
-
if (!process.stdin.isTTY) {
|
|
1477
|
-
console.error(
|
|
1478
|
-
chalk10.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
|
|
1479
|
-
);
|
|
1480
|
-
process.exit(1);
|
|
1481
|
-
}
|
|
1482
|
-
try {
|
|
1483
|
-
return await inquirer3.prompt(questions);
|
|
1484
|
-
} catch (err) {
|
|
1485
|
-
if (isStreamClosed(err)) {
|
|
1486
|
-
console.error(chalk10.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
|
|
1487
|
-
process.exit(1);
|
|
1488
|
-
}
|
|
1489
|
-
throw err;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
function isStreamClosed(err) {
|
|
1493
|
-
const e = err;
|
|
1494
|
-
return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
|
|
1495
|
-
}
|
|
1496
|
-
function installPromptCrashGuard() {
|
|
1497
|
-
process.on("uncaughtException", (err) => {
|
|
1498
|
-
if (isStreamClosed(err)) {
|
|
1499
|
-
console.error(
|
|
1500
|
-
chalk10.red(
|
|
1501
|
-
"Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
|
|
1502
|
-
)
|
|
1503
|
-
);
|
|
1504
|
-
process.exit(1);
|
|
1505
|
-
}
|
|
1506
|
-
throw err;
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
// src/commands/secrets.ts
|
|
1511
1559
|
var secretsCommand = new Command9("secrets").description("Manage app secrets");
|
|
1512
1560
|
secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
|
|
1513
1561
|
const apiKey = getApiKey();
|
|
@@ -2340,7 +2388,7 @@ function period(start, end) {
|
|
|
2340
2388
|
// src/index.ts
|
|
2341
2389
|
installPromptCrashGuard();
|
|
2342
2390
|
var program = new Command18();
|
|
2343
|
-
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");
|
|
2344
2392
|
program.addCommand(loginCommand);
|
|
2345
2393
|
program.addCommand(logoutCommand);
|
|
2346
2394
|
program.addCommand(initCommand);
|