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)
This commit is contained in:
81
src/flags.ts
Normal file
81
src/flags.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user