clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

main
nzip / cli / commands / vault.ts
vault.ts 7.5K
1import { ApiClient } from "../lib/api.ts";2import type { Ttl, VaultLifecycle } from "@nzip/shared";3import type { Config } from "../lib/config.ts";4import { assertVaultAllowed, saveConfig } from "../lib/config.ts";5import { bold, dim, emit, fail, green, parseTtl, table } from "../lib/fmt.ts";6import { renameVault } from "../lib/paths.ts";78export interface VaultFlags {9 slot?: number;10 newName?: string;11 description?: string;12 clearDescription?: boolean;13 defaultTtl?: string;14 defaultFor?: string;15 clearDefaultFor?: boolean;16 maxTtl?: string;17 requirePassword?: boolean;18 defaultPasswordFile?: string;19 clearDefaultPassword?: boolean;20}2122async function policyFlags(flags: VaultFlags) {23 if (flags.defaultPasswordFile && flags.clearDefaultPassword) {24 fail("choose --default-password-file or --no-default-password");25 }26 const maxTtl = flags.maxTtl === undefined27 ? undefined28 : flags.maxTtl === "none"29 ? null30 : parseTtl(flags.maxTtl);31 if (maxTtl === "forever" || (typeof maxTtl === "number" && (maxTtl <= 0 || maxTtl > 3650))) {32 fail("--max-ttl must be 1-3650 days or none");33 }34 // Read a file rather than putting a shared visitor password in process arguments.35 const defaultPassword = flags.clearDefaultPassword36 ? null37 : flags.defaultPasswordFile38 ? (await Deno.readTextFile(flags.defaultPasswordFile)).replace(/\r?\n$/, "")39 : undefined;40 return { maxTtl, requirePassword: flags.requirePassword, defaultPassword };41}4243function defaultTtlFlag(raw: string | undefined): Ttl | null | undefined {44 if (raw === undefined) return undefined;45 if (raw === "inherit") return null;46 try {47 return parseTtl(raw);48 } catch (cause) {49 return fail((cause as Error).message.replace("--ttl", "--default-ttl"));50 }51}5253function lifecycleFlag(flags: VaultFlags): VaultLifecycle | null | undefined {54 if (flags.clearDefaultFor && flags.defaultFor !== undefined) {55 fail("pass either --default-for or --no-default-for, not both");56 }57 if (flags.clearDefaultFor) return null;58 if (flags.defaultFor === undefined) return undefined;59 if (flags.defaultFor !== "temporary" && flags.defaultFor !== "permanent") {60 fail('--default-for must be "temporary" or "permanent"');61 }62 return flags.defaultFor;63}6465/**66 * A bare `--description` (value swallowed by the shell) parses to "", which67 * would otherwise silently clear the stored text — require `--no-description`68 * for that instead.69 */70function descriptionFlag(flags: VaultFlags): string | undefined {71 if (flags.clearDescription) {72 if (flags.description !== undefined) {73 fail("pass either --description TEXT or --no-description, not both");74 }75 return "";76 }77 if (flags.description !== undefined && flags.description.trim() === "") {78 fail("--description requires text; use --no-description to clear it");79 }80 return flags.description;81}8283export async function cmdVault(84 config: Config,85 rest: string[],86 flags: VaultFlags,87): Promise<void> {88 const [sub, name, third, ...extra] = rest;89 if (extra.length > 0) fail(`too many arguments for nzip vault ${sub ?? ""}`);90 const api = new ApiClient(config);91 const { slot, newName } = flags;9293 if (sub === "ls" || sub === undefined) {94 if (name !== undefined) fail("usage: nzip vault ls");95 const status = await api.status();96 const vaults = status.vaults;97 emit(() => {98 if (vaults.length === 0) {99 console.log(dim("no vaults registered — run `nzip vault add <name>`"));100 return;101 }102 console.log(table(103 [104 "SLOT",105 "VAULT",106 "SITES",107 "DEFAULT TTL",108 "MAX TTL",109 "ACCESS",110 "DEFAULT FOR",111 "DESCRIPTION",112 ],113 vaults.map((v) => [114 `0x${v.slot.toString(16)}`,115 v.defaultFor.length > 0 ? bold(v.name) : v.name,116 String(v.siteCount),117 String(v.effectiveDefaultTtl),118 v.maxTtl == null ? "none" : String(v.maxTtl),119 v.requirePassword120 ? "password required"121 : v.hasDefaultPassword122 ? "password default"123 : "optional",124 v.defaultFor.join(","),125 v.description ?? "",126 ]),127 ));128 }, {129 ok: true,130 defaultVaults: status.defaultVaults,131 globalDefaultTtl: status.globalDefaultTtl,132 vaults,133 });134 return;135 }136137 if (sub === "add") {138 if (!name || third !== undefined) {139 fail("usage: nzip vault add <name> [--slot N] [--description TEXT]");140 }141 const description = descriptionFlag(flags) || undefined;142 try {143 assertVaultAllowed(name, config);144 } catch (e) {145 return fail((e as Error).message);146 }147 const v = await api.createVault(148 name,149 slot,150 description,151 defaultTtlFlag(flags.defaultTtl),152 lifecycleFlag(flags),153 await policyFlags(flags),154 );155 const madeDefault = !config.defaultVault;156 if (madeDefault) await saveConfig({ ...config, defaultVault: v.name });157 emit(() => {158 console.log(159 `${green("✓")} vault ${bold(v.name)} registered at slot 0x${v.slot.toString(16)}`,160 );161 if (madeDefault) console.log(dim(` set as default vault`));162 }, {163 ok: true,164 ...v,165 default: madeDefault || config.defaultVault === v.name,166 });167 return;168 }169170 if (sub === "update") {171 if (172 !name || third !== undefined ||173 (newName === undefined && flags.description === undefined && !flags.clearDescription) &&174 flags.defaultTtl === undefined && flags.defaultFor === undefined &&175 !flags.clearDefaultFor && flags.maxTtl === undefined &&176 flags.requirePassword === undefined &&177 flags.defaultPasswordFile === undefined && !flags.clearDefaultPassword178 ) {179 fail(180 "usage: nzip vault update <name> [--name NEW_NAME] [--description TEXT | --no-description] [--default-ttl 14d|forever|inherit]",181 );182 }183 const description = descriptionFlag(flags);184 try {185 assertVaultAllowed(name, config);186 if (newName !== undefined) assertVaultAllowed(newName, config);187 } catch (e) {188 return fail((e as Error).message);189 }190 const v = await api.updateVault(name, {191 ...await policyFlags(flags),192 name: newName,193 description,194 defaultTtl: defaultTtlFlag(flags.defaultTtl),195 defaultFor: lifecycleFlag(flags),196 });197 if (newName !== undefined && newName !== name) {198 await saveConfig({199 ...config,200 ...(config.defaultVault === name ? { defaultVault: newName } : {}),201 ...(config.allowVaults202 ? { allowVaults: config.allowVaults.map((vault) => vault === name ? newName : vault) }203 : {}),204 });205 await renameVault(name, newName);206 }207 emit(208 () =>209 console.log(210 `${green("✓")} vault ${bold(name)} updated${newName ? ` as ${bold(newName)}` : ""}`,211 ),212 { ok: true, ...v },213 );214 return;215 }216217 if (sub === "default") {218 if ((name !== "temporary" && name !== "permanent") || !third) {219 fail("usage: nzip vault default <temporary|permanent> <name>");220 }221 try {222 assertVaultAllowed(third, config);223 } catch (e) {224 return fail((e as Error).message);225 }226 const result = await api.setDefaultVault(name, third);227 if (name === "temporary") await saveConfig({ ...config, defaultVault: third });228 emit(229 () => console.log(`${green("✓")} ${name} default vault set to ${bold(third)}`),230 { ok: true, ...result },231 );232 return;233 }234235 fail(`unknown vault subcommand: ${sub} (use ls | add | update | default)`);236}