app/cli/lib/credentials.ts
zzmq.dev@gmail.com/zz / app / cli / lib / credentials.ts
@ 31943d52
Download
Ladder Summary
- file This module manages the local storage and retrieval of the user’s Cloudflare‑API credentials for the **zz** CLI (reading, writing, clearing a JSON file in ~/.zz/credentials.json, exposing the default API base, and constructing an authenticated git clone URL). Exported symbols are: - **ZzCredentials** (interface) - **DEFAULT_API_BASE** (string) - **readCredentials()** - **requireCredentials()** - **clearCredentials()** - **writeCredentials(creds: ZzCredentials)** - **apiBase()** - **authedCloneUrl(base: string, token: string, slug: string)**.
// Credentials for the REMOTE (Cloudflare) backend's JSON API -- a
// bearer token from `zz login`, separate from config.ts's
// "which repos do I care about" local config (a different concern:
// this is who you are to the remote API, that's what you watch).
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { resolveUserPath } from "./paths.js";
export interface ZzCredentials {
apiBase: string;
token: string;
username: string; // the real identity -- no separate internal id (see worker migration 0006)
}
// The deployed Worker's current URL -- the one thing in the CLI that
// really is a hardcoded default, since a fresh `zz login` run has no
// other way to know where to start. Override with ZZ_API_BASE the
// moment the domain changes (zzbin.dev/zzmq.{com,dev}); nothing else
// in the CLI or server hardcodes a domain.
export const DEFAULT_API_BASE = "https://zz-maintainer.thoward37.workers.dev";
function credentialsPath(): string {
return process.env.ZZ_CREDENTIALS ? resolveUserPath(process.env.ZZ_CREDENTIALS) : join(homedir(), ".zz", "credentials.json");
}
export function readCredentials(): ZzCredentials | null {
const path = credentialsPath();
if (!existsSync(path)) return null;
return JSON.parse(readFileSync(path, "utf8")) as ZzCredentials;
}
export function requireCredentials(): ZzCredentials {
const creds = readCredentials();
if (!creds) throw new Error(`not logged in -- run \`zz login\` first (looked for credentials at ${credentialsPath()})`);
return creds;
}
export function writeCredentials(creds: ZzCredentials): void {
const path = credentialsPath();
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, JSON.stringify(creds, null, 2) + "\n", "utf8");
try {
chmodSync(path, 0o600); // best-effort; a real secret, same spirit as ~/.ssh keys
} catch {
// chmod can fail on some filesystems (e.g. certain Windows setups) -- not fatal
}
}
export function apiBase(): string {
return process.env.ZZ_API_BASE ?? readCredentials()?.apiBase ?? DEFAULT_API_BASE;
}
/** A real `git clone`-able URL with the bearer token embedded as Basic Auth userinfo, against the zz-maintainer Worker's own git smart-HTTP proxy (not a raw Artifacts URL -- see site/content/git-compatibility). Shared by `zz repo clone` and the remote work loop's own ensureCloned, so there's one place this convention lives. */
export function authedCloneUrl(base: string, token: string, slug: string): string {
const host = base.replace(/^https?:\/\//, "");
return `https://${encodeURIComponent(token)}@${host}/${slug}`;
}