Tracer bullet for gitea-axi: runnable npm package on axi-sdk-js with gitea-js as the sole HTTP layer, ESM on Node 20+. - issue list with --state/--limit, default fields, count line from X-Total-Count, type=issues guard, explicit empty state, and next-step suggestions - repo context detection from the git origin remote (SSH/scp/HTTPS), tea credential discovery with the three-way login-matching split, and -R/--repo and --login overrides (flag > env > auto) - token retrieval via tea login helper get: tea's login list JSON carries no token (ADR 0001 amended) - full AxiError classification table with path-based 404 split, TOON errors on stdout, exit codes 0/1/2 - test mode (GITEA_AXI_API_URL/TOKEN/REPO) suppressing subprocesses, fixture server, and vitest suites driving the CLI seam (50 tests)
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import { axiError } from "./errors.js";
|
|
|
|
export interface FlagSpec {
|
|
/** Flag names (e.g. "--state") mapped to whether they take a value. */
|
|
[name: string]: { takesValue: boolean };
|
|
}
|
|
|
|
export interface ParsedFlags {
|
|
flags: Record<string, string | true>;
|
|
positionals: string[];
|
|
}
|
|
|
|
export interface SplitFlag {
|
|
name: string;
|
|
inlineValue?: string;
|
|
}
|
|
|
|
/** Split "--flag=value" into name and inline value; "--flag" has none. */
|
|
export function splitFlag(arg: string): SplitFlag {
|
|
const equals = arg.indexOf("=");
|
|
if (equals === -1) {
|
|
return { name: arg };
|
|
}
|
|
return { name: arg.slice(0, equals), inlineValue: arg.slice(equals + 1) };
|
|
}
|
|
|
|
/**
|
|
* Resolve a value-taking flag's value from its inline form or the next
|
|
* argument, returning the index of the last argument consumed.
|
|
*/
|
|
export function consumeFlagValue(
|
|
args: string[],
|
|
index: number,
|
|
flag: SplitFlag,
|
|
suggestions: string[] = [],
|
|
): { value: string; lastIndex: number } {
|
|
if (flag.inlineValue !== undefined) {
|
|
if (!flag.inlineValue) {
|
|
throw axiError(`Flag ${flag.name} requires a value`, "VALIDATION_ERROR", suggestions);
|
|
}
|
|
return { value: flag.inlineValue, lastIndex: index };
|
|
}
|
|
const next = args[index + 1];
|
|
if (next === undefined || next.startsWith("-")) {
|
|
throw axiError(`Flag ${flag.name} requires a value`, "VALIDATION_ERROR", suggestions);
|
|
}
|
|
return { value: next, lastIndex: index + 1 };
|
|
}
|
|
|
|
export function parseFlags(
|
|
args: string[],
|
|
spec: FlagSpec,
|
|
helpCommand: string,
|
|
): ParsedFlags {
|
|
const flags: Record<string, string | true> = {};
|
|
const positionals: string[] = [];
|
|
const helpSuggestion = [`Run \`gitea-axi ${helpCommand} --help\` to see available flags`];
|
|
for (let i = 0; i < args.length; i++) {
|
|
const arg = args[i]!;
|
|
if (!arg.startsWith("-")) {
|
|
positionals.push(arg);
|
|
continue;
|
|
}
|
|
const flag = splitFlag(arg);
|
|
const entry = spec[flag.name];
|
|
if (!entry) {
|
|
throw axiError(`Unknown flag: ${flag.name}`, "VALIDATION_ERROR", helpSuggestion);
|
|
}
|
|
if (!entry.takesValue) {
|
|
if (flag.inlineValue !== undefined) {
|
|
throw axiError(`Flag ${flag.name} does not take a value`, "VALIDATION_ERROR", helpSuggestion);
|
|
}
|
|
flags[flag.name] = true;
|
|
continue;
|
|
}
|
|
const consumed = consumeFlagValue(args, i, flag, helpSuggestion);
|
|
flags[flag.name] = consumed.value;
|
|
i = consumed.lastIndex;
|
|
}
|
|
return { flags, positionals };
|
|
}
|