Files
gitea-axi/src/flags.ts
alexion 38026f963d feat: scaffold CLI and minimal issue list (task 0001)
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)
2026-07-11 07:11:07 -04:00

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 };
}