- 加入 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)
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
/** 命令定義:名稱、說明、參數與執行。供 help 與分發使用。 */
|
||
|
||
export interface ParsedArgs {
|
||
/** 位置參數(子命令後的非選項值)。 */
|
||
positionals: string[];
|
||
/** 選項旗標(--key value 或 --flag)。 */
|
||
flags: Record<string, string | boolean>;
|
||
}
|
||
|
||
export interface CommandContext {
|
||
args: ParsedArgs;
|
||
/** 輸出 JSON 到 stdout(唯一 stdout 內容)。 */
|
||
print: (value: unknown) => void;
|
||
}
|
||
|
||
export interface Command {
|
||
/** 子命令路徑,例如 ["tokens", "verify"]。 */
|
||
path: string[];
|
||
summary: string;
|
||
usage: string;
|
||
/** 位置參數說明,例如 ["<slug>"]。與 usage 一致即可。 */
|
||
run: (ctx: CommandContext) => Promise<void> | void;
|
||
}
|
||
|
||
/** 解析 argv:--key value、--key=value、--flag(布林)、-- 結束選項。 */
|
||
export function parseArgs(argv: string[]): ParsedArgs {
|
||
const positionals: string[] = [];
|
||
const flags: Record<string, string | boolean> = {};
|
||
let i = 0;
|
||
let noMoreFlags = false;
|
||
while (i < argv.length) {
|
||
const arg = argv[i++]!;
|
||
if (!noMoreFlags && arg === "--") {
|
||
noMoreFlags = true;
|
||
continue;
|
||
}
|
||
if (!noMoreFlags && arg.startsWith("--")) {
|
||
const eq = arg.indexOf("=");
|
||
if (eq !== -1) {
|
||
const key = arg.slice(2, eq);
|
||
const value = arg.slice(eq + 1);
|
||
flags[key] = value;
|
||
} else {
|
||
const key = arg.slice(2);
|
||
const next = argv[i];
|
||
if (next !== undefined && !next.startsWith("--")) {
|
||
flags[key] = next;
|
||
i += 1;
|
||
} else {
|
||
flags[key] = true;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
positionals.push(arg);
|
||
}
|
||
return { positionals, flags };
|
||
}
|
||
|
||
export function flagString(flags: Record<string, string | boolean>, key: string): string | undefined {
|
||
const v = flags[key];
|
||
return typeof v === "string" ? v : undefined;
|
||
}
|
||
|
||
/** 註冊表:以完整路徑字串("tokens verify")索引。 */
|
||
export class Registry {
|
||
private readonly commands = new Map<string, Command>();
|
||
|
||
add(command: Command): void {
|
||
this.commands.set(command.path.join(" "), command);
|
||
}
|
||
|
||
/** 找出命令;prefix 可指向群組(回傳其子命令列表供 help)。 */
|
||
find(prefix: string[]): Command | undefined {
|
||
return this.commands.get(prefix.join(" "));
|
||
}
|
||
|
||
/** 列出 prefix 下的下一層子命令(供群組 help)。 */
|
||
children(prefix: string[]): Command[] {
|
||
return Array.from(this.commands.values()).filter((c) => {
|
||
if (c.path.length !== prefix.length + 1) return false;
|
||
return prefix.every((p, idx) => c.path[idx] === p);
|
||
});
|
||
}
|
||
|
||
all(): Command[] {
|
||
return Array.from(this.commands.values());
|
||
}
|
||
}
|