← Public packages
@kentcdodds/friction-log
Qualify and file friction papercuts to GitHub (any owner/repo) or wake Patch for Kody packages; daily sweep of kody-hq/kody friction issues.
src/issues.ts
107 lines · 3.2 KB · TypeScriptimport githubRequest from 'kody:@kentcdodds/github/request'
import { frictionLabel, owner, repo, type FrictionIssue, type IssueComment } from './shared.ts'
type GithubRequestResult = {
ok?: boolean
status?: number
data?: unknown
text?: string
}
type GithubIssue = {
number?: number
title?: string | null
body?: string | null
html_url?: string | null
user?: { login?: string | null } | null
created_at?: string | null
updated_at?: string | null
pull_request?: unknown
labels?: Array<{ name?: string } | string>
}
type GithubComment = {
id?: number
body?: string | null
created_at?: string | null
user?: { login?: string | null } | null
}
function requireOk(result: GithubRequestResult, path: string) {
if (result?.ok === false || (result.status && result.status >= 400)) {
throw new Error(
`GitHub ${path} failed (${result.status}): ${String(result.text || '').slice(0, 200)}`,
)
}
return result.data
}
function asIssues(data: unknown): Array<GithubIssue> {
return Array.isArray(data) ? (data as Array<GithubIssue>) : []
}
function asComments(data: unknown): Array<GithubComment> {
return Array.isArray(data) ? (data as Array<GithubComment>) : []
}
async function listLabeledIssues() {
const issues: Array<GithubIssue> = []
for (let page = 1; page <= 10; page += 1) {
const path = `/repos/${owner}/${repo}/issues?labels=${encodeURIComponent(frictionLabel)}&state=open&per_page=100&page=${page}`
const data = requireOk(await githubRequest({ account: 'bot', path }), path)
const pageIssues = asIssues(data).filter((issue) => !issue.pull_request)
issues.push(...pageIssues)
if (asIssues(data).length < 100) break
}
return issues
}
async function listIssueComments(issueNumber: number) {
const comments: Array<GithubComment> = []
for (let page = 1; page <= 10; page += 1) {
const path = `/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`
const data = requireOk(await githubRequest({ account: 'bot', path }), path)
const pageComments = asComments(data)
comments.push(...pageComments)
if (pageComments.length < 100) break
}
return comments
}
function toComment(comment: GithubComment): IssueComment {
return {
id: Number(comment.id || 0),
userLogin: String(comment.user?.login || ''),
createdAt: String(comment.created_at || ''),
body: String(comment.body || ''),
}
}
function issueLabels(issue: GithubIssue) {
return (issue.labels || [])
.map((label) => (typeof label === 'string' ? label : String(label?.name || '')))
.filter(Boolean)
}
export async function loadFrictionIssues(): Promise<Array<FrictionIssue>> {
const listed = await listLabeledIssues()
const issues: Array<FrictionIssue> = []
for (const issue of listed) {
const number = Number(issue.number || 0)
if (!number) continue
const comments = await listIssueComments(number)
issues.push({
number,
title: String(issue.title || ''),
body: String(issue.body || ''),
htmlUrl: String(issue.html_url || ''),
userLogin: String(issue.user?.login || ''),
createdAt: String(issue.created_at || ''),
updatedAt: String(issue.updated_at || ''),
labels: issueLabels(issue),
comments: comments.map(toComment),
})
}
issues.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt))
return issues
}