102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
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<string, unknown>, ...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<string> {
|
|
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<AuthContext> {
|
|
const raw = authStdin
|
|
? (JSON.parse(await readStdin()) as Record<string, unknown>)
|
|
: {
|
|
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<string, unknown>).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<RuntimeContext> {
|
|
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;
|
|
}
|
|
|