← Public packages
@kody/github
Call GitHub REST, GraphQL, and pull requests with a GitHub OAuth App or a personal access token.
src/pr/set-review-status.ts
110 lines · 3.3 KB · TypeScriptimport { graphql, isConfirmed, isDryRun, pickAuthInput } from '../core.ts'
import getPrInfo from './get-info.ts'
import { normalizePrLocator, normalizeSetPrReviewStatusInput } from './pr-input.ts'
const CONVERT_TO_DRAFT = `
mutation ConvertToDraft($prId: ID!) {
convertPullRequestToDraft(input: { pullRequestId: $prId }) {
pullRequest { id number title isDraft }
}
}
`
const MARK_READY = `
mutation MarkReadyForReview($prId: ID!) {
markPullRequestReadyForReview(input: { pullRequestId: $prId }) {
pullRequest { id number title isDraft }
}
}
`
/**
* Set a pull request to draft, ready-for-review, or toggle between them.
*
* Pass `dryRun: true` to preview without calling GitHub. Live status changes
* require `confirm: true`.
*
* @param params - PR locator plus `status: 'draft' | 'ready' | 'toggle'`,
* optional `integrationName` or `secretName`, `dryRun`, and `confirm`.
* @returns Whether the status changed and the updated draft flag.
* @example
* import setPrReviewStatus from 'kody:@kody/github/pr/set-review-status'
* const preview = await setPrReviewStatus({ owner: 'example-org', repo: 'example', prNumber: 1, status: 'ready', dryRun: true })
*/
export default async function setPrReviewStatus(params: Record<string, unknown> = {}) {
const auth = pickAuthInput(params)
if (params.status === 'toggle') {
const locator = normalizePrLocator(params)
const pr = await getPrInfo({ ...locator, ...auth })
const status = pr.draft ? 'ready' : 'draft'
return setPrReviewStatus({ ...locator, ...auth, status, dryRun: params.dryRun, confirm: params.confirm })
}
const { status, ...locator } = normalizeSetPrReviewStatusInput(params)
const pr = await getPrInfo({ ...locator, ...auth })
if (pr.state !== 'open') {
throw new Error(
`PR #${pr.number} is ${pr.state}, not open — cannot change review status.`,
)
}
const alreadyCorrect =
(status === 'draft' && pr.draft) || (status === 'ready' && !pr.draft)
if (alreadyCorrect) {
return {
changed: false,
prNumber: pr.number,
title: pr.title,
url: pr.url,
draft: pr.draft,
message: `PR #${pr.number} is already ${pr.draft ? 'a draft' : 'ready for review'}.`,
auth: pr.auth,
}
}
if (isDryRun(params)) {
return {
dryRun: true,
wouldChange: true,
prNumber: pr.number,
title: pr.title,
url: pr.url,
fromDraft: pr.draft,
toDraft: status === 'draft',
message: `Would mark PR #${pr.number} as ${status === 'draft' ? 'a draft' : 'ready for review'}.`,
auth,
}
}
if (!isConfirmed(params)) {
throw new Error(
`Changing review status on ${locator.owner}/${locator.repo}#${pr.number} requires dryRun: true (preview) or confirm: true (live write).`,
)
}
const result = await graphql<Record<string, Record<string, { pullRequest: Record<string, unknown> }>>>({
...auth,
query: status === 'draft' ? CONVERT_TO_DRAFT : MARK_READY,
variables: { prId: pr.nodeId },
throwOnError: true,
confirm: true,
})
const updated =
status === 'draft'
? result.data!.convertPullRequestToDraft.pullRequest
: result.data!.markPullRequestReadyForReview.pullRequest
return {
changed: true,
prNumber: pr.number,
title: pr.title,
url: pr.url,
draft: updated.isDraft as boolean,
message: `PR #${pr.number} is now ${updated.isDraft ? 'a draft' : 'ready for review'}.`,
auth: result.auth,
}
}