Skip to content

src/pulls.ts

106 lines · 3.6 KB · TypeScript
Raw
import { originRequest } from './request.ts'
import { type OriginPullSummary } from './types.ts'

/**
 * List pull requests for an Origin repo the app installation can see.
 *
 * @param params.ownerSlug - Origin owner slug (for example `kentcdodds`).
 * @param params.repoName - Repository name.
 * @param params.installationId - Optional installation id override.
 * @returns `{ items }` of slim pull summaries (number, title, state, head, base, url).
 * @example
 * import listOriginPulls from 'kody:@kentcdodds/origin/pulls'
 * const { items } = await listOriginPulls({ ownerSlug: 'kentcdodds', repoName: 'kody' })
 * // => { items: [{ number: 12, title: '...', state: 'open', head: 'feat', base: 'main', url: '...' }] }
 */
export async function listOriginPulls(params: {
	ownerSlug: string
	repoName: string
	installationId?: string
}): Promise<{ items: Array<OriginPullSummary> }> {
	const ownerSlug = params.ownerSlug.trim()
	const repoName = params.repoName.trim()
	if (!ownerSlug || !repoName) {
		throw new Error('ownerSlug and repoName are required.')
	}
	const result = await originRequest({
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls`,
		auth: 'installation',
		installationId: params.installationId,
	})
	if (!result.ok) {
		const message =
			isRecord(result.body) && typeof result.body.message === 'string'
				? result.body.message
				: `HTTP ${String(result.status)}`
		throw new Error(`Failed to list Origin pull requests: ${message}`)
	}
	const payload = isRecord(result.body) ? result.body : {}
	const raw = Array.isArray(payload.pulls)
		? payload.pulls
		: Array.isArray(payload.items)
			? payload.items
			: Array.isArray(result.body)
				? result.body
				: []
	return {
		items: raw.map((item) => slimPull(asRecord(item))),
	}
}

export async function getOriginPull(params: {
	ownerSlug: string
	repoName: string
	pullNumber: string | number
	installationId?: string
}): Promise<OriginPullSummary> {
	const ownerSlug = params.ownerSlug.trim()
	const repoName = params.repoName.trim()
	const pullNumber = String(params.pullNumber).trim()
	if (!ownerSlug || !repoName || !pullNumber) {
		throw new Error('ownerSlug, repoName, and pullNumber are required.')
	}
	const result = await originRequest({
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls/${encodeURIComponent(pullNumber)}`,
		auth: 'installation',
		installationId: params.installationId,
	})
	if (!result.ok) {
		const message =
			isRecord(result.body) && typeof result.body.message === 'string'
				? result.body.message
				: `HTTP ${String(result.status)}`
		throw new Error(`Failed to get Origin pull request: ${message}`)
	}
	return slimPull(asRecord(result.body))
}

export default listOriginPulls

function slimPull(item: Record<string, unknown>): OriginPullSummary {
	const head = isRecord(item.head) ? item.head : null
	const base = isRecord(item.base) ? item.base : null
	return {
		number:
			typeof item.number === 'string' || typeof item.number === 'number'
				? item.number
				: null,
		title: stringField(item.title),
		state: stringField(item.state),
		head: stringField(head?.ref) ?? stringField(item.head),
		base: stringField(base?.ref) ?? stringField(item.base),
		url: stringField(item.url) ?? stringField(item.htmlUrl),
	}
}

function asRecord(value: unknown): Record<string, unknown> {
	return isRecord(value) ? value : {}
}

function isRecord(value: unknown): value is Record<string, unknown> {
	return value !== null && typeof value === 'object' && !Array.isArray(value)
}

function stringField(value: unknown): string | null {
	return typeof value === 'string' && value.length > 0 ? value : null
}