Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/origin

src/pulls.ts

256 lines · 6.7 KB · TypeScript
import {
	asRecord,
	booleanField,
	isRecord,
	originErrorMessage,
	stringField,
} from './http.ts'
import { originRequest } from './request.ts'
import {
	type OriginAccountInput,
	type OriginMutationInput,
	type OriginPullSummary,
} from './types.ts'

/**
 * List pull requests on an Origin repository.
 */
export async function listOriginPulls(
	params: OriginAccountInput & {
		ownerSlug: string
		repoName: 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({
		...params,
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls`,
		auth: 'installation',
	})
	if (!result.ok) {
		throw new Error(
			`Failed to list Origin pull requests: ${originErrorMessage(result.body, result.status)}`,
		)
	}
	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))),
	}
}

/**
 * Fetch one Origin pull request.
 */
export async function getOriginPull(
	params: OriginAccountInput & {
		ownerSlug: string
		repoName: string
		pullNumber: string | number
	},
): 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({
		...params,
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls/${encodeURIComponent(pullNumber)}`,
		auth: 'installation',
	})
	if (!result.ok) {
		throw new Error(
			`Failed to get Origin pull request: ${originErrorMessage(result.body, result.status)}`,
		)
	}
	return slimPull(asRecord(result.body))
}

/**
 * Create an Origin pull request. Pass `dryRun: true` to preview, or
 * `confirm: true` to create it.
 */
export async function createOriginPull(
	params: OriginAccountInput &
		OriginMutationInput & {
			ownerSlug: string
			repoName: string
			title: string
			head: string
			base: string
			body?: string
			draft?: boolean
			parentPullNumber?: string | number
		},
) {
	const ownerSlug = params.ownerSlug.trim()
	const repoName = params.repoName.trim()
	const title = params.title.trim()
	const head = params.head.trim()
	const base = params.base.trim()
	if (!ownerSlug || !repoName || !title || !head || !base) {
		throw new Error('ownerSlug, repoName, title, head, and base are required.')
	}
	const result = await originRequest({
		...params,
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls`,
		method: 'POST',
		auth: 'installation',
		body: {
			title,
			head,
			base,
			...(params.body !== undefined ? { body: params.body } : {}),
			...(params.draft !== undefined ? { draft: params.draft } : {}),
			...(params.parentPullNumber !== undefined
				? { parentPullNumber: String(params.parentPullNumber) }
				: {}),
		},
	})
	if (result.dryRun) {
		return {
			dryRun: true,
			wouldCreate: true,
			ownerSlug,
			repoName,
			title,
			head,
			base,
		}
	}
	if (!result.ok) {
		throw new Error(
			`Failed to create Origin pull request: ${originErrorMessage(result.body, result.status)}`,
		)
	}
	return slimPull(asRecord(result.body))
}

/**
 * Update an Origin pull request. Pass `dryRun: true` to preview, or
 * `confirm: true` to apply.
 */
export async function updateOriginPull(
	params: OriginAccountInput &
		OriginMutationInput & {
			ownerSlug: string
			repoName: string
			pullNumber: string | number
			title?: string
			body?: string
			state?: string
			draft?: boolean
		},
) {
	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 body: Record<string, unknown> = {}
	if (params.title !== undefined) body.title = params.title
	if (params.body !== undefined) body.body = params.body
	if (params.state !== undefined) body.state = params.state
	if (params.draft !== undefined) body.draft = params.draft
	const result = await originRequest({
		...params,
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls/${encodeURIComponent(pullNumber)}`,
		method: 'PATCH',
		auth: 'installation',
		body,
	})
	if (result.dryRun) {
		return {
			dryRun: true,
			wouldUpdate: true,
			ownerSlug,
			repoName,
			pullNumber,
			body,
		}
	}
	if (!result.ok) {
		throw new Error(
			`Failed to update Origin pull request: ${originErrorMessage(result.body, result.status)}`,
		)
	}
	return slimPull(asRecord(result.body))
}

/**
 * Merge an Origin pull request. Pass `dryRun: true` to preview, or
 * `confirm: true` to merge.
 */
export async function mergeOriginPull(
	params: OriginAccountInput &
		OriginMutationInput & {
			ownerSlug: string
			repoName: string
			pullNumber: string | number
			expectedHeadSha?: string
		},
) {
	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({
		...params,
		path: `/repos/${encodeURIComponent(ownerSlug)}/${encodeURIComponent(repoName)}/pulls/${encodeURIComponent(pullNumber)}/merge`,
		method: 'POST',
		auth: 'installation',
		body: params.expectedHeadSha
			? { expectedHeadSha: params.expectedHeadSha }
			: {},
	})
	if (result.dryRun) {
		return {
			dryRun: true,
			wouldMerge: true,
			ownerSlug,
			repoName,
			pullNumber,
		}
	}
	if (!result.ok) {
		throw new Error(
			`Failed to merge Origin pull request: ${originErrorMessage(result.body, result.status)}`,
		)
	}
	return 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),
		draft: booleanField(item.draft),
		merged: booleanField(item.merged),
		head: stringField(head?.ref) ?? stringField(item.head),
		base: stringField(base?.ref) ?? stringField(item.base),
		url: stringField(item.url) ?? stringField(item.htmlUrl),
	}
}