feat: add pr edit, close, and reopen (task 0011)
All checks were successful
CI / test (pull_request) Successful in 38s
CI / test (push) Successful in 39s

Add the PR-side state mutations mirroring the issue-side slice:

- `pr edit` applies title/body/base/milestone and the recomputed assignee
  list in one PATCH, with additive label endpoints and (per the ADR 0007
  amendment) the dedicated requested-reviewers POST/DELETE endpoints for
  `--add-reviewer`/`--remove-reviewer`.
- `pr close --comment` posts the comment after the PATCH and surfaces a
  comment-post failure; an already-closed or merged PR is an `already: true`
  no-op reporting the actual state.
- `pr reopen` is an `already: true` no-op when already open.

Extract the fetch-then-patch assignee merge into a shared `src/assignees.ts`
(`mergeAssignees` + `assigneeLogins`), now used by both `issue edit` and
`pr edit`.
This commit was merged in pull request #11.
This commit is contained in:
2026-07-13 19:55:00 -04:00
parent 6c15e6082f
commit 965ece306f
7 changed files with 808 additions and 29 deletions

44
src/assignees.ts Normal file
View File

@@ -0,0 +1,44 @@
/** The subset of a Gitea `User` this module reads: just the login, if present. */
interface AssigneeLike {
login?: string;
}
/**
* The logins of an entity's current assignees, dropping any without one. The
* shared read side of fetch-then-patch: `issue edit` and `pr edit` both take the
* `assignees` off a freshly fetched entity and feed the result to
* {@link mergeAssignees}.
*/
export function assigneeLogins(assignees: AssigneeLike[] | undefined): string[] {
return (assignees ?? []).flatMap((assignee) => (assignee.login ? [assignee.login] : []));
}
/**
* The full assignee login list to PATCH under fetch-then-patch semantics
* (ADR 0007): the entity's current assignees with the requested additions
* appended and removals dropped. Matching is case-insensitive and the result is
* de-duplicated, order-preserving, so a login already assigned never lands in the
* list twice. Shared by `issue edit` and `pr edit`, whose PATCH bodies both
* replace the whole assignee list rather than adding or removing individual
* entries.
*/
export function mergeAssignees(current: string[], add: string[], remove: string[]): string[] {
const removeSet = new Set(remove.map((login) => login.toLowerCase()));
const seen = new Set<string>();
const result: string[] = [];
const push = (login: string): void => {
const key = login.toLowerCase();
if (removeSet.has(key) || seen.has(key)) {
return;
}
seen.add(key);
result.push(login);
};
for (const login of current) {
push(login);
}
for (const login of add) {
push(login);
}
return result;
}