feat: add issue create and comment (task 0004)
All checks were successful
CI / test (pull_request) Successful in 24s
CI / test (push) Successful in 28s

Introduce the first mutations, along with the shared machinery the later
issue and PR mutation slices reuse.

- `issue create` with --title/--body/--body-file/--assignee/--label/
  --milestone/--fields, emitting `issue: { number, title, state, url }`
- `issue comment <n>`, echoing the created comment from the POST response
  with the body cleaned and truncated at 800 chars
- body-source resolution (--body vs --body-file), label and milestone
  name->ID lookup, repeatable flags, and the `joined`/`selectExtraFields`
  field extractors

Label lookup pages until exhausted, since a repo with more labels than one
page would otherwise fail to resolve a valid name. The end-to-end tier seeds
a mixed-case label and milestone and passes both in a different case, so the
case-insensitive lookup is verified against live Gitea rather than only
against fixtures.
This commit was merged in pull request #3.
This commit is contained in:
2026-07-11 20:39:01 -04:00
parent 5e66b4d746
commit f82414a933
13 changed files with 1328 additions and 41 deletions

View File

@@ -1,4 +1,9 @@
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect } from "vitest";
import { runCli } from "../src/cli.js";
import type { FixtureServer } from "./fixture-server.js";
export interface CliResult {
stdout: string;
@@ -40,3 +45,40 @@ export function testModeEnv(apiUrl: string): Record<string, string> {
GITEA_AXI_REPO: "testowner/testrepo",
};
}
/**
* Throwaway files for the `--body-file` paths, cleaned up together. Call
* {@link TempFiles.write} to create one and {@link TempFiles.cleanup} from an
* `afterEach`.
*/
export interface TempFiles {
write: (name: string, content: string) => string;
cleanup: () => void;
}
export function tempFiles(): TempFiles {
const dirs: string[] = [];
return {
write: (name, content) => {
const dir = mkdtempSync(join(tmpdir(), "gitea-axi-test-"));
dirs.push(dir);
const path = join(dir, name);
writeFileSync(path, content, "utf8");
return path;
},
cleanup: () => {
for (const dir of dirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
},
};
}
/** The parsed body of the single POST the CLI sent to `path`; fails if it sent none. */
export function postedBody(server: FixtureServer, path: string): Record<string, unknown> {
const post = server.requests.find(
(request) => request.method === "POST" && request.path === path,
);
expect(post, `expected a POST to ${path}`).toBeDefined();
return post!.body as Record<string, unknown>;
}