import { randomUUID } from "node:crypto"; import { DEFAULT_SERVER, DEFAULT_TIMEOUT } from "./constants.js"; import { CliError } from "./errors.js"; import { requireValue, parseIntStrict } from "./options.js"; import type { AuthContext, GlobalArgs, ParsedGlobalArgs, RuntimeContext } from "./types.js"; function pick(source: Record, ...keys: string[]): string | null { for (const key of keys) { const value = source[key]; if (value !== undefined && value !== null && value !== "") return String(value); } return null; } function readStdin(): Promise { return new Promise((resolve, reject) => { let body = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk: string) => { body += chunk; }); process.stdin.on("end", () => resolve(body)); process.stdin.on("error", reject); }); } export async function loadAuthContext(authStdin: boolean): Promise { const raw = authStdin ? (JSON.parse(await readStdin()) as Record) : { server: process.env.TJWATER_SERVER, access_token: process.env.TJWATER_ACCESS_TOKEN, project_id: process.env.TJWATER_PROJECT_ID, user_id: process.env.TJWATER_USER_ID, username: process.env.TJWATER_USERNAME, network: process.env.TJWATER_NETWORK, headers: process.env.TJWATER_EXTRA_HEADERS ? JSON.parse(process.env.TJWATER_EXTRA_HEADERS) : {}, }; const headers = (raw.headers ?? {}) as unknown; if (!headers || Array.isArray(headers) || typeof headers !== "object") { throw new CliError("认证失败", "AUTH_CONTEXT_INVALID", "auth context headers must be a JSON object", 3); } return { server: pick(raw, "server", "base_url"), accessToken: pick(raw, "access_token", "token", "accessToken"), projectId: pick(raw, "project_id", "projectId", "x_project_id"), userId: pick(raw, "user_id", "userId", "x_user_id"), username: pick(raw, "username", "preferred_username"), network: pick(raw, "network", "project_code", "projectCode", "project"), headers: Object.fromEntries(Object.entries(headers as Record).map(([key, value]) => [String(key), String(value)])), }; } export function parseGlobalArgs(argv: string[]): ParsedGlobalArgs { const globals: GlobalArgs = { server: null, authStdin: false, scheme: null, timeout: DEFAULT_TIMEOUT, requestId: null, }; const rest: string[] = []; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]!; if (arg === "--auth-stdin") globals.authStdin = true; else if (arg === "--server") globals.server = requireValue(argv, ++i, "--server"); else if (arg === "--scheme") globals.scheme = requireValue(argv, ++i, "--scheme"); else if (arg === "--timeout") globals.timeout = parseIntStrict(requireValue(argv, ++i, "--timeout"), "--timeout"); else if (arg === "--request-id") globals.requestId = requireValue(argv, ++i, "--request-id"); else rest.push(arg); } return { globals, rest }; } export async function buildRuntime(globals: GlobalArgs): Promise { const auth = await loadAuthContext(globals.authStdin); return { server: globals.server || auth.server || DEFAULT_SERVER, auth, scheme: globals.scheme, timeout: globals.timeout, requestId: globals.requestId || randomUUID(), }; } export function requireNetwork(ctx: RuntimeContext): string { if (ctx.auth.network) return ctx.auth.network; throw new CliError("认证失败", "NETWORK_CONTEXT_REQUIRED", "missing network in auth context for legacy network-based endpoints", 3, false, null, ["add network to auth context"]); } export function requireUsername(ctx: RuntimeContext): string { if (ctx.auth.username) return ctx.auth.username; throw new CliError("认证失败", "USERNAME_CONTEXT_REQUIRED", "missing username in auth context", 3, false, null, ["add username to auth context"]); } export function resolveScheme(ctx: RuntimeContext, explicit: string | undefined, must = false): string | null { const scheme = explicit || ctx.scheme; if (must && !scheme) throw new CliError("CLI 参数错误", "SCHEME_REQUIRED", "missing scheme; use --scheme", 2); return scheme ?? null; }