Skip to content

src/repos.ts

118 lines · 3.7 KB · TypeScript
Raw
import { originRequest } from './request.ts'
import { type OriginRepoSummary } from './types.ts'

/**
 * List Origin repositories for an owner the app installation can see.
 *
 * @param params.ownerSlug - Origin owner slug (for example `kentcdodds`).
 * @param params.pageSize - Optional page size.
 * @param params.pageToken - Optional pagination token from a previous call.
 * @param params.filter - Optional Origin list filter string.
 * @param params.installationId - Optional installation id override.
 * @returns `{ items, nextPageToken }` of slim repo summaries.
 * @example
 * import listOriginRepos from 'kody:@kentcdodds/origin/repos'
 * const { items } = await listOriginRepos({ ownerSlug: 'kentcdodds', pageSize: 5 })
 * // => { items: [{ id: '...', name: 'kody', fullName: 'kentcdodds/kody', ... }], nextPageToken: null }
 */
export async function listOriginRepos(params: {
	ownerSlug: string
	pageSize?: number
	pageToken?: string
	filter?: string
	installationId?: string
}): Promise<{ items: Array<OriginRepoSummary>; nextPageToken: string | null }> {
	const ownerSlug = params.ownerSlug.trim()
	if (!ownerSlug) {
		throw new Error('ownerSlug is required.')
	}
	const result = await originRequest({
		path: `/repos/${encodeURIComponent(ownerSlug)}`,
		auth: 'installation',
		installationId: params.installationId,
		query: {
			pageSize: params.pageSize,
			pageToken: params.pageToken,
			filter: params.filter,
		},
	})
	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 repos: ${message}`)
	}
	const payload = isRecord(result.body) ? result.body : {}
	const raw = Array.isArray(payload.repositories) ? payload.repositories : []
	return {
		items: raw.map((item) => slimRepo(asRecord(item))),
		nextPageToken:
			typeof payload.nextPageToken === 'string' && payload.nextPageToken
				? payload.nextPageToken
				: null,
	}
}

export async function getOriginRepo(params: {
	ownerSlug: string
	repoName: string
	installationId?: string
}): Promise<OriginRepoSummary> {
	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)}`,
		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 repo: ${message}`)
	}
	return slimRepo(asRecord(result.body))
}

export default listOriginRepos

function slimRepo(item: Record<string, unknown>): OriginRepoSummary {
	const owner = isRecord(item.owner) ? item.owner : null
	const mirror = isRecord(item.mirror) ? item.mirror : null
	return {
		id: stringField(item.id) ?? '',
		name: stringField(item.name) ?? '',
		fullName: stringField(item.fullName),
		owner: owner
			? {
					slug: stringField(owner.slug) ?? '',
					id: stringField(owner.id) ?? '',
				}
			: null,
		defaultBranch: stringField(item.defaultBranch),
		cloneUrl: stringField(item.cloneUrl),
		mirror: mirror
			? {
					source: stringField(mirror.source),
					status: stringField(mirror.status),
				}
			: null,
	}
}

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
}