clone

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

feat/source-stable-push
nzip / cli / commands / vault.ts
vault.ts 6.0K
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}1718function defaultTtlFlag(raw: string | undefined): Ttl | null | undefined {19 if (raw === undefined) return undefined;20 if (raw === "inherit") return null;21 try {22 return parseTtl(raw);23 } catch (cause) {24 return fail((cause as Error).message.replace("--ttl", "--default-ttl"));25 }26}2728function lifecycleFlag(flags: VaultFlags): VaultLifecycle | null | undefined {29 if (flags.clearDefaultFor && flags.defaultFor !== undefined) {30 fail("pass either --default-for or --no-default-for, not both");31 }32 if (flags.clearDefaultFor) return null;33 if (flags.defaultFor === undefined) return undefined;34 if (flags.defaultFor !== "temporary" && flags.defaultFor !== "permanent") {35 fail('--default-for must be "temporary" or "permanent"');36 }37 return flags.defaultFor;38}3940/**41 * A bare `--description` (value swallowed by the shell) parses to "", which42 * would otherwise silently clear the stored text — require `--no-description`43 * for that instead.44 */45function descriptionFlag(flags: VaultFlags): string | undefined {46 if (flags.clearDescription) {47 if (flags.description !== undefined) {48 fail("pass either --description TEXT or --no-description, not both");49 }50 return "";51 }52 if (flags.description !== undefined && flags.description.trim() === "") {53 fail("--description requires text; use --no-description to clear it");54 }55 return flags.description;56}5758export async function cmdVault(59 config: Config,60 rest: string[],61 flags: VaultFlags,62): Promise<void> {63 const [sub, name, third, ...extra] = rest;64 if (extra.length > 0) fail(`too many arguments for nzip vault ${sub ?? ""}`);65 const api = new ApiClient(config);66 const { slot, newName } = flags;6768 if (sub === "ls" || sub === undefined) {69 if (name !== undefined) fail("usage: nzip vault ls");70 const status = await api.status();71 const vaults = status.vaults;72 emit(() => {73 if (vaults.length === 0) {74 console.log(dim("no vaults registered — run `nzip vault add <name>`"));75 return;76 }77 console.log(table(78 ["SLOT", "VAULT", "SITES", "DEFAULT TTL", "DEFAULT FOR", "DESCRIPTION"],79 vaults.map((v) => [80 `0x${v.slot.toString(16)}`,81 v.defaultFor.length > 0 ? bold(v.name) : v.name,82 String(v.siteCount),83 String(v.effectiveDefaultTtl),84 v.defaultFor.join(","),85 v.description ?? "",86 ]),87 ));88 }, {89 ok: true,90 defaultVaults: status.defaultVaults,91 globalDefaultTtl: status.globalDefaultTtl,92 vaults,93 });94 return;95 }9697 if (sub === "add") {98 if (!name || third !== undefined) {99 fail("usage: nzip vault add <name> [--slot N] [--description TEXT]");100 }101 const description = descriptionFlag(flags) || undefined;102 try {103 assertVaultAllowed(name, config);104 } catch (e) {105 return fail((e as Error).message);106 }107 const v = await api.createVault(108 name,109 slot,110 description,111 defaultTtlFlag(flags.defaultTtl),112 lifecycleFlag(flags),113 );114 const madeDefault = !config.defaultVault;115 if (madeDefault) await saveConfig({ ...config, defaultVault: v.name });116 emit(() => {117 console.log(118 `${green("✓")} vault ${bold(v.name)} registered at slot 0x${v.slot.toString(16)}`,119 );120 if (madeDefault) console.log(dim(` set as default vault`));121 }, {122 ok: true,123 ...v,124 default: madeDefault || config.defaultVault === v.name,125 });126 return;127 }128129 if (sub === "update") {130 if (131 !name || third !== undefined ||132 (newName === undefined && flags.description === undefined && !flags.clearDescription) &&133 flags.defaultTtl === undefined && flags.defaultFor === undefined &&134 !flags.clearDefaultFor135 ) {136 fail(137 "usage: nzip vault update <name> [--name NEW_NAME] [--description TEXT | --no-description] [--default-ttl 14d|forever|inherit]",138 );139 }140 const description = descriptionFlag(flags);141 try {142 assertVaultAllowed(name, config);143 if (newName !== undefined) assertVaultAllowed(newName, config);144 } catch (e) {145 return fail((e as Error).message);146 }147 const v = await api.updateVault(name, {148 name: newName,149 description,150 defaultTtl: defaultTtlFlag(flags.defaultTtl),151 defaultFor: lifecycleFlag(flags),152 });153 if (newName !== undefined && newName !== name) {154 await saveConfig({155 ...config,156 ...(config.defaultVault === name ? { defaultVault: newName } : {}),157 ...(config.allowVaults158 ? { allowVaults: config.allowVaults.map((vault) => vault === name ? newName : vault) }159 : {}),160 });161 await renameVault(name, newName);162 }163 emit(164 () =>165 console.log(166 `${green("✓")} vault ${bold(name)} updated${newName ? ` as ${bold(newName)}` : ""}`,167 ),168 { ok: true, ...v },169 );170 return;171 }172173 if (sub === "default") {174 if ((name !== "temporary" && name !== "permanent") || !third) {175 fail("usage: nzip vault default <temporary|permanent> <name>");176 }177 try {178 assertVaultAllowed(third, config);179 } catch (e) {180 return fail((e as Error).message);181 }182 const result = await api.setDefaultVault(name, third);183 if (name === "temporary") await saveConfig({ ...config, defaultVault: third });184 emit(185 () => console.log(`${green("✓")} ${name} default vault set to ${bold(third)}`),186 { ok: true, ...result },187 );188 return;189 }190191 fail(`unknown vault subcommand: ${sub} (use ls | add | update | default)`);192}