import ingestMissing from './ingest-missing.ts'
import { getRepoSettings } from './storage.ts'
type WebhookInput = {
request?: {
headers?: Record<string, string>
json?: unknown
body?: string
}
}
type GithubPushPayload = {
ref?: string
after?: string
repository?: { full_name?: string }
commits?: Array<{ id?: string }>
}
/**
* Ingest new main-branch commits from a GitHub push webhook.
* Platform HMAC verification happens before this export runs.
*
* @param input - Kody webhook envelope with the GitHub push JSON body
* @returns Whether the delivery was ignored or which SHAs were ingested
*
* @example
* import handleGithubWebhook from 'kody:@kentcdodds/lineage/handle-github-webhook'
*
* const result = await handleGithubWebhook({
* request: { json: { ref: 'refs/heads/main', commits: [{ id: 'abc' }] } },
* })
*/
export default async function handleGithubWebhook(input: WebhookInput = {}) {
const payload = (input.request?.json ?? {}) as GithubPushPayload
const headers = input.request?.headers ?? {}
const event = headers['x-github-event'] || headers['X-GitHub-Event']
if (event && event !== 'push') {
return { ok: true, ignored: true, reason: `event:${event}` }
}
const repo = await getRepoSettings()
const expectedRef = `refs/heads/${repo.branch}`
if (payload.ref && payload.ref !== expectedRef) {
return { ok: true, ignored: true, reason: `ref:${payload.ref}` }
}
const remoteName = payload.repository?.full_name
const expectedName = `${repo.owner}/${repo.repo}`
if (remoteName && remoteName.toLowerCase() !== expectedName.toLowerCase()) {
return { ok: true, ignored: true, reason: `repo:${remoteName}` }
}
const shas = [
...new Set(
[
...(payload.commits ?? []).map((commit) => commit.id),
payload.after,
].filter((sha): sha is string => Boolean(sha) && sha !== '0'.repeat(40)),
),
]
if (shas.length === 0) {
return ingestMissing({ limit: 10, maxPages: 1 })
}
return ingestMissing({ shas })
}