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:
55
src/git.ts
Normal file
55
src/git.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { CliDeps } from "./deps.js";
|
||||
import { runSubprocess } from "./subprocess.js";
|
||||
|
||||
export interface RemoteRepo {
|
||||
host: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function parseRepoPath(rawPath: string): { owner: string; name: string } | null {
|
||||
const path = rawPath.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
||||
const segments = path.split("/");
|
||||
if (segments.length !== 2 || !segments[0] || !segments[1]) {
|
||||
return null;
|
||||
}
|
||||
return { owner: segments[0], name: segments[1] };
|
||||
}
|
||||
|
||||
export function parseRemoteUrl(url: string): RemoteRepo | null {
|
||||
const trimmed = url.trim();
|
||||
if (/^(https?|ssh):\/\//.test(trimmed)) {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(trimmed);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const repo = parseRepoPath(parsed.pathname);
|
||||
if (!repo || !parsed.hostname) {
|
||||
return null;
|
||||
}
|
||||
return { host: parsed.hostname, ...repo };
|
||||
}
|
||||
// scp-like SSH form: [user@]host:owner/name[.git]
|
||||
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/.exec(trimmed);
|
||||
if (scp) {
|
||||
const repo = parseRepoPath(scp[2]!);
|
||||
if (!repo) {
|
||||
return null;
|
||||
}
|
||||
return { host: scp[1]!, ...repo };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function detectRemote(deps: CliDeps): Promise<RemoteRepo | null> {
|
||||
const result = await runSubprocess("git", ["remote", "get-url", "origin"], {
|
||||
cwd: deps.cwd,
|
||||
env: deps.env,
|
||||
});
|
||||
if (result.enoent || result.code !== 0) {
|
||||
return null;
|
||||
}
|
||||
return parseRemoteUrl(result.stdout);
|
||||
}
|
||||
Reference in New Issue
Block a user