- 加入 pnpm workspace;Node.js ≥22 + TypeScript 實作 - 指令:auth status、tokens verify/create/list/revoke、novels get - 非互動、stdout 僅輸出單一 JSON;exit code 0/1/2 - 設定優先順序:--token/--api-url > FOX_API_TOKEN/FOX_API_URL > ~/.config/fox/cli.json - novels list/create/update 待後端接受 Bearer token 後補齊(README 授權缺口) - 已對模擬後端完成端到端驗證(含 401/404/網路錯誤、冪等 revoke)
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
||
import { homedir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { ApiError } from "./errors.js";
|
||
|
||
/** CLI 設定:API 位址與 access token。 */
|
||
export interface FoxConfig {
|
||
apiUrl: string;
|
||
token: string | null;
|
||
}
|
||
|
||
const DEFAULT_API_URL = "http://localhost:3000";
|
||
|
||
/**
|
||
* 讀取設定。優先順序(高 → 低):
|
||
* 1. 環境變數 FOX_API_URL / FOX_API_TOKEN
|
||
* 2. --api-url / --token 參數(由 caller 傳入)
|
||
* 3. 本機設定檔 ~/.config/fox/cli.json({"apiUrl": "...", "token": "..."})
|
||
*
|
||
* apiUrl 預設 http://localhost:3000;token 找不到時為 null,
|
||
* 需要 token 的命令由 caller 檢查並報錯。
|
||
*/
|
||
export async function loadConfig(flags: { apiUrl?: string; token?: string }): Promise<FoxConfig> {
|
||
const configPath = join(homedir(), ".config", "fox", "cli.json");
|
||
let fileApiUrl: string | undefined;
|
||
let fileToken: string | undefined;
|
||
try {
|
||
const raw = await readFile(configPath, "utf-8");
|
||
const parsed: unknown = JSON.parse(raw);
|
||
if (parsed && typeof parsed === "object") {
|
||
const obj = parsed as Record<string, unknown>;
|
||
if (typeof obj.apiUrl === "string") fileApiUrl = obj.apiUrl;
|
||
if (typeof obj.token === "string") fileToken = obj.token;
|
||
}
|
||
} catch (err) {
|
||
const code = (err as NodeJS.ErrnoException).code;
|
||
if (code !== "ENOENT") {
|
||
// 設定檔存在但壞掉:明確報錯,而不是靜默忽略。
|
||
throw new ApiError(`設定檔 ${configPath} 無法解析:${(err as Error).message}`);
|
||
}
|
||
}
|
||
|
||
const envApiUrl = process.env.FOX_API_URL?.trim() || undefined;
|
||
const envToken = process.env.FOX_API_TOKEN?.trim() || undefined;
|
||
|
||
const apiUrl =
|
||
flags.apiUrl ??
|
||
envApiUrl ??
|
||
fileApiUrl ??
|
||
DEFAULT_API_URL;
|
||
const token = flags.token ?? envToken ?? fileToken ?? null;
|
||
return { apiUrl: apiUrl.replace(/\/+$/, ""), token };
|
||
}
|