Issues
Calls
const open = await forge.issues.list("owner", "repo", { state: "open", page: 1, perPage: 30 });
const found = await forge.issues.search("owner", "repo", "hydration mismatch", { state: "all" });
const one = await forge.issues.get("owner", "repo", 42);
const comments = await forge.issues.listComments("owner", "repo", 42, { perPage: 50 });
const comment = await forge.issues.getComment("owner", "repo", 42, comments.items[0]!.id);
const created = await forge.issues.create("owner", "repo", {
title: "Broken link on the deploy page",
body: "The Cloudflare link 404s.",
labels: ["docs"],
assignees: ["someone"],
});
ListOptions is page, perPage and state, and state is open, closed or all.
The shape
interface Issue {
id: string;
number: number; // GitLab iid, not the global id
title: string;
body: string;
state: "open" | "closed";
labels: string[];
author: { login: string };
assignees: { login: string }[];
createdAt: string;
updatedAt: string;
url: string;
}
GitHub's /issues endpoint returns pull requests too, which is a trap the first time. They are filtered out here by the missing pull_request key, so issues.list on GitHub is issues only.
Search
search keeps the platform's own syntax. GitHub qualifiers like label:bug is:open work on GitHub. GitLab and Gitea treat the whole string as text, so label:bug there searches for the literal string. The result is a SearchPageResult, a PageResult plus incomplete, true when the platform admits the answer is partial.
An empty query is a ForgesError with status 400 before any request goes out.
Comments
listComments reads the discussion oldest first. On GitHub and Gitea issues and pull requests share the endpoint, both index pull requests as issues. On GitLab the notes are asked for with an explicit ascending sort, and two kinds are dropped: system notes about label and state churn, and inline diff notes, which belong to review threads. So a short GitLab page with hasNextPage: true is normal, keep paging. Gitea sends the whole discussion at once and the page you asked for is cut locally.
interface Comment {
id: string;
body: string;
author: { login: string };
url: string;
createdAt: string;
updatedAt: string;
}
Assignees
assignees on create takes logins. GitLab Free accepts one. Pass two and you get a 400 before the request leaves, instead of a confusing answer from GitLab.