@videan/cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +128 -53
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -120,8 +120,8 @@ var CloudClient = class {
|
|
|
120
120
|
const res = await this.request("GET", "/plans");
|
|
121
121
|
return res.data;
|
|
122
122
|
}
|
|
123
|
-
async request(method,
|
|
124
|
-
const url = `${this.baseUrl}${
|
|
123
|
+
async request(method, path5, body) {
|
|
124
|
+
const url = `${this.baseUrl}${path5}`;
|
|
125
125
|
const headers = {
|
|
126
126
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
127
127
|
"Content-Type": "application/json"
|
|
@@ -290,8 +290,8 @@ var AppsNamespace = class {
|
|
|
290
290
|
if (opts?.instance) params.set("instance", opts.instance);
|
|
291
291
|
if (opts?.nextToken) params.set("next_token", opts.nextToken);
|
|
292
292
|
const query = params.toString();
|
|
293
|
-
const
|
|
294
|
-
const res = await this.client.request("GET",
|
|
293
|
+
const path5 = `/apps/${appId}/logs${query ? `?${query}` : ""}`;
|
|
294
|
+
const res = await this.client.request("GET", path5);
|
|
295
295
|
return res.data;
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
@@ -934,35 +934,100 @@ var logoutCommand = new Command2("logout").description("Forget the saved key, or
|
|
|
934
934
|
import { Command as Command3 } from "commander";
|
|
935
935
|
import inquirer2 from "inquirer";
|
|
936
936
|
import chalk3 from "chalk";
|
|
937
|
+
import fs2 from "fs-extra";
|
|
938
|
+
import path2 from "node:path";
|
|
939
|
+
|
|
940
|
+
// src/videan-md.ts
|
|
937
941
|
import fs from "fs-extra";
|
|
938
942
|
import path from "node:path";
|
|
939
|
-
var
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
943
|
+
var VIDEAN_MD = `# This project deploys to Videan
|
|
944
|
+
|
|
945
|
+
Deploy: \`videan deploy\` (from this directory). Logs: \`videan logs -f\`.
|
|
946
|
+
Undo a bad deploy: \`videan rollback\`. Full agent guide: https://videan.xyz/llms.txt
|
|
947
|
+
|
|
948
|
+
The contract: serve HTTP on **port 3000**. Runtime (Node/Python/Next.js/Go/
|
|
949
|
+
Dockerfile) is detected from the files \u2014 no config needed. If the app has a
|
|
950
|
+
managed database, read \`DATABASE_URL\` from the environment. Log to stdout.
|
|
951
|
+
|
|
952
|
+
## Auth \u2014 read this before writing any login code
|
|
953
|
+
|
|
954
|
+
This platform puts a sign-in gate in front of apps unless they are public.
|
|
955
|
+
|
|
956
|
+
- **Gated app (visibility private/org/authenticated \u2014 the default): DO NOT
|
|
957
|
+
build a login page.** Every request arrives with the visitor already
|
|
958
|
+
authenticated: read \`X-User-Id\`, \`X-User-Email\`, \`X-User-Roles\`. If the
|
|
959
|
+
app needs its own user records, find-or-create a local user from
|
|
960
|
+
\`X-User-Email\` and start your session from that \u2014 never render a login
|
|
961
|
+
form when the header is present.
|
|
962
|
+
- **Public app** (\`videan apps set --public\`): the gate is out of the path,
|
|
963
|
+
no headers arrive, bring your own auth if the app needs any. Required for
|
|
964
|
+
anything that must receive webhooks from external services.
|
|
965
|
+
|
|
966
|
+
Secrets: \`videan secrets set KEY=value\` (UPPERCASE keys) \u2014 injected as env
|
|
967
|
+
vars on the next deploy. Never commit credentials to this repo instead.
|
|
968
|
+
`;
|
|
969
|
+
var POINTER = "\n## Videan\n\nThis project deploys to Videan \u2014 see VIDEAN.md for the platform contract (port 3000, auth headers, deploy commands) before writing server or login code.\n";
|
|
970
|
+
async function writeVideanMd(dir) {
|
|
971
|
+
const wrote = [];
|
|
972
|
+
const target = path.join(dir, "VIDEAN.md");
|
|
973
|
+
if (!await fs.pathExists(target)) {
|
|
974
|
+
await fs.writeFile(target, VIDEAN_MD, "utf-8");
|
|
975
|
+
wrote.push("VIDEAN.md");
|
|
976
|
+
}
|
|
977
|
+
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
978
|
+
const p = path.join(dir, name);
|
|
979
|
+
if (!await fs.pathExists(p)) continue;
|
|
980
|
+
const body = await fs.readFile(p, "utf-8");
|
|
981
|
+
if (body.includes("VIDEAN.md")) continue;
|
|
982
|
+
await fs.appendFile(p, POINTER, "utf-8");
|
|
983
|
+
wrote.push(`${name} (pointer)`);
|
|
984
|
+
}
|
|
985
|
+
return wrote;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/commands/init.ts
|
|
989
|
+
var initCommand = new Command3("init").description("Initialize a new Cloud app in the current directory").option("--name <name>", "App name (defaults to the directory name)").option("--runtime <runtime>", "One of: " + SUPPORTED_RUNTIMES.join(", ")).option("--entrypoint <file>", "Entry file", "index.ts").option("--yes", "Take the defaults without prompting").action(async (opts) => {
|
|
990
|
+
const configPath2 = path2.join(process.cwd(), CONFIG_FILE_NAME);
|
|
991
|
+
const interactive = process.stdin.isTTY && !opts.yes;
|
|
992
|
+
if (await fs2.pathExists(configPath2)) {
|
|
993
|
+
if (!interactive) {
|
|
994
|
+
console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
|
|
995
|
+
} else {
|
|
996
|
+
console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
|
|
997
|
+
const { overwrite } = await inquirer2.prompt([
|
|
998
|
+
{ type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
|
|
999
|
+
]);
|
|
1000
|
+
if (!overwrite) {
|
|
1001
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
947
1005
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1006
|
+
if (!await fs2.pathExists(configPath2) || interactive) {
|
|
1007
|
+
const answers = interactive ? await inquirer2.prompt([
|
|
1008
|
+
{ type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
|
|
1009
|
+
{
|
|
1010
|
+
type: "list",
|
|
1011
|
+
name: "runtime",
|
|
1012
|
+
message: "Runtime:",
|
|
1013
|
+
choices: SUPPORTED_RUNTIMES
|
|
1014
|
+
},
|
|
1015
|
+
{ type: "input", name: "entrypoint", message: "Entrypoint:", default: opts.entrypoint }
|
|
1016
|
+
]) : {
|
|
1017
|
+
name: opts.name || path2.basename(process.cwd()),
|
|
1018
|
+
runtime: opts.runtime || SUPPORTED_RUNTIMES[0],
|
|
1019
|
+
entrypoint: opts.entrypoint
|
|
1020
|
+
};
|
|
1021
|
+
const config2 = [
|
|
1022
|
+
`name: ${answers.name}`,
|
|
1023
|
+
`runtime: ${answers.runtime}`,
|
|
1024
|
+
`entrypoint: ${answers.entrypoint}`,
|
|
1025
|
+
""
|
|
1026
|
+
].join("\n");
|
|
1027
|
+
await fs2.writeFile(configPath2, config2, "utf-8");
|
|
1028
|
+
console.log(chalk3.green(`Created ${CONFIG_FILE_NAME}`));
|
|
1029
|
+
}
|
|
1030
|
+
for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
|
|
966
1031
|
});
|
|
967
1032
|
|
|
968
1033
|
// src/commands/deploy.ts
|
|
@@ -971,8 +1036,8 @@ import chalk5 from "chalk";
|
|
|
971
1036
|
import ora2 from "ora";
|
|
972
1037
|
|
|
973
1038
|
// src/util.ts
|
|
974
|
-
import
|
|
975
|
-
import
|
|
1039
|
+
import fs3 from "fs-extra";
|
|
1040
|
+
import path3 from "node:path";
|
|
976
1041
|
import ignore from "ignore";
|
|
977
1042
|
import chalk4 from "chalk";
|
|
978
1043
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -989,30 +1054,30 @@ async function collectFiles(dir) {
|
|
|
989
1054
|
".DS_Store",
|
|
990
1055
|
"*.log"
|
|
991
1056
|
]);
|
|
992
|
-
const gitignorePath =
|
|
993
|
-
if (await
|
|
994
|
-
const content = await
|
|
1057
|
+
const gitignorePath = path3.join(dir, ".gitignore");
|
|
1058
|
+
if (await fs3.pathExists(gitignorePath)) {
|
|
1059
|
+
const content = await fs3.readFile(gitignorePath, "utf-8");
|
|
995
1060
|
ig.add(content);
|
|
996
1061
|
}
|
|
997
|
-
const cloudignorePath =
|
|
998
|
-
if (await
|
|
999
|
-
const content = await
|
|
1062
|
+
const cloudignorePath = path3.join(dir, ".cloudignore");
|
|
1063
|
+
if (await fs3.pathExists(cloudignorePath)) {
|
|
1064
|
+
const content = await fs3.readFile(cloudignorePath, "utf-8");
|
|
1000
1065
|
ig.add(content);
|
|
1001
1066
|
}
|
|
1002
1067
|
const files = {};
|
|
1003
1068
|
const binaryFiles = {};
|
|
1004
1069
|
const skipped = [];
|
|
1005
1070
|
async function walk(currentDir) {
|
|
1006
|
-
const entries = await
|
|
1071
|
+
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
1007
1072
|
for (const entry of entries) {
|
|
1008
|
-
const fullPath =
|
|
1009
|
-
const relativePath =
|
|
1073
|
+
const fullPath = path3.join(currentDir, entry.name);
|
|
1074
|
+
const relativePath = path3.relative(dir, fullPath);
|
|
1010
1075
|
const ignorePath = entry.isDirectory() ? `${relativePath}/` : relativePath;
|
|
1011
1076
|
if (ig.ignores(ignorePath)) continue;
|
|
1012
1077
|
if (entry.isDirectory()) {
|
|
1013
1078
|
await walk(fullPath);
|
|
1014
1079
|
} else if (entry.isFile()) {
|
|
1015
|
-
const buffer = await
|
|
1080
|
+
const buffer = await fs3.readFile(fullPath);
|
|
1016
1081
|
if (buffer.byteLength > MAX_FILE_BYTES) {
|
|
1017
1082
|
skipped.push({
|
|
1018
1083
|
path: relativePath,
|
|
@@ -1118,6 +1183,16 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
|
|
|
1118
1183
|
}
|
|
1119
1184
|
if (current.status === "running") {
|
|
1120
1185
|
spinner.succeed(chalk5.green(`Deployed successfully!`));
|
|
1186
|
+
try {
|
|
1187
|
+
const app = await client4.apps.get(appId);
|
|
1188
|
+
const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
|
|
1189
|
+
console.log(
|
|
1190
|
+
gated ? chalk5.gray(
|
|
1191
|
+
` Access: ${app.visibility || "org"} \u2014 visitors sign in once at the edge; your app receives X-User-Id / X-User-Email on every request. Don't build a login page.`
|
|
1192
|
+
) : chalk5.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
|
|
1193
|
+
);
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1121
1196
|
if (current.url) {
|
|
1122
1197
|
const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
|
|
1123
1198
|
console.log(chalk5.cyan(` URL: ${url}`));
|
|
@@ -1689,8 +1764,8 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
|
|
|
1689
1764
|
// src/commands/storage.ts
|
|
1690
1765
|
import { Command as Command13 } from "commander";
|
|
1691
1766
|
import chalk14 from "chalk";
|
|
1692
|
-
import
|
|
1693
|
-
import
|
|
1767
|
+
import fs4 from "fs-extra";
|
|
1768
|
+
import path4 from "node:path";
|
|
1694
1769
|
var storageCommand = new Command13("storage").description("Store files for an app");
|
|
1695
1770
|
function client() {
|
|
1696
1771
|
const apiKey = getApiKey();
|
|
@@ -1737,12 +1812,12 @@ ${objects.length} object(s), ${human(total)}`));
|
|
|
1737
1812
|
});
|
|
1738
1813
|
storageCommand.command("put <file> [key]").description("Upload a file (key defaults to the file's name)").option("-a, --app <id>", "App ID").option("-t, --type <mime>", "Content type").action(async (file, key, opts) => {
|
|
1739
1814
|
const appId = requireApp(opts.app);
|
|
1740
|
-
if (!await
|
|
1815
|
+
if (!await fs4.pathExists(file)) {
|
|
1741
1816
|
console.error(chalk14.red(`No such file: ${file}`));
|
|
1742
1817
|
process.exit(1);
|
|
1743
1818
|
}
|
|
1744
|
-
const body = await
|
|
1745
|
-
const objectKey = key ||
|
|
1819
|
+
const body = await fs4.readFile(file);
|
|
1820
|
+
const objectKey = key || path4.basename(file);
|
|
1746
1821
|
const stored = await client().storage.upload(appId, objectKey, body, {
|
|
1747
1822
|
contentType: opts.type
|
|
1748
1823
|
});
|
|
@@ -1754,8 +1829,8 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
|
|
|
1754
1829
|
storageCommand.command("get <key> [file]").description("Download an object (defaults to the key's basename)").option("-a, --app <id>", "App ID").action(async (key, file, opts) => {
|
|
1755
1830
|
const appId = requireApp(opts.app);
|
|
1756
1831
|
const bytes = await client().storage.download(appId, key);
|
|
1757
|
-
const dest = file ||
|
|
1758
|
-
await
|
|
1832
|
+
const dest = file || path4.basename(key);
|
|
1833
|
+
await fs4.writeFile(dest, bytes);
|
|
1759
1834
|
console.log(chalk14.green(`Wrote ${dest}`) + chalk14.dim(` (${human(bytes.length)})`));
|
|
1760
1835
|
});
|
|
1761
1836
|
storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
|
|
@@ -1791,7 +1866,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
|
|
|
1791
1866
|
// src/commands/email.ts
|
|
1792
1867
|
import { Command as Command14 } from "commander";
|
|
1793
1868
|
import chalk15 from "chalk";
|
|
1794
|
-
import
|
|
1869
|
+
import fs5 from "fs-extra";
|
|
1795
1870
|
var emailCommand = new Command14("email").description("Send email from an app");
|
|
1796
1871
|
function client2() {
|
|
1797
1872
|
const apiKey = getApiKey();
|
|
@@ -1812,7 +1887,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
|
|
|
1812
1887
|
let text = opts.text;
|
|
1813
1888
|
let html = opts.html;
|
|
1814
1889
|
if (opts.bodyFile) {
|
|
1815
|
-
const contents = await
|
|
1890
|
+
const contents = await fs5.readFile(opts.bodyFile, "utf-8");
|
|
1816
1891
|
if (/\.html?$/i.test(opts.bodyFile)) html = contents;
|
|
1817
1892
|
else text = contents;
|
|
1818
1893
|
}
|
|
@@ -1857,7 +1932,7 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
|
|
|
1857
1932
|
// src/commands/jobs.ts
|
|
1858
1933
|
import { Command as Command15 } from "commander";
|
|
1859
1934
|
import chalk16 from "chalk";
|
|
1860
|
-
import
|
|
1935
|
+
import fs6 from "fs-extra";
|
|
1861
1936
|
var jobsCommand = new Command15("jobs").description("Enqueue and inspect background jobs");
|
|
1862
1937
|
function client3() {
|
|
1863
1938
|
const apiKey = getApiKey();
|
|
@@ -1893,7 +1968,7 @@ function statusLabel(status) {
|
|
|
1893
1968
|
jobsCommand.command("enqueue").description("Queue a job for the app to run").option("-a, --app <id>", "App ID").option("-q, --queue <name>", "Queue name (default: default)").option("-p, --path <path>", "Path on the app to POST to (default: /jobs)").option("-d, --data <json>", "JSON payload").option("-f, --file <path>", "Read the JSON payload from a file").option("--delay <seconds>", "Run no earlier than this many seconds from now").option("--attempts <n>", "Attempts before the job is given up on (default: 3)").option("--timeout <seconds>", "How long the handler gets per attempt (default: 60)").option("--key <key>", "Idempotency key -- a repeat returns the same job").option("--json", "Print the raw response").action(async (opts) => {
|
|
1894
1969
|
const appId = requireApp3(opts.app);
|
|
1895
1970
|
let payload;
|
|
1896
|
-
const raw = opts.file ? await
|
|
1971
|
+
const raw = opts.file ? await fs6.readFile(opts.file, "utf-8") : opts.data;
|
|
1897
1972
|
if (raw !== void 0) {
|
|
1898
1973
|
try {
|
|
1899
1974
|
payload = JSON.parse(raw);
|