(args: {
prNumbers: number[]
githubToken: string
fnLabel: string
selection: string
nodeSchema: z.ZodType<T>
})
| 296 | // finalize comments. We therefore fail loud when any error is present, and warn |
| 297 | // on the (genuine) drops so they leave a trace. |
| 298 | export async function fetchPRNodes<T extends { number: number }>(args: { |
| 299 | prNumbers: number[] |
| 300 | githubToken: string |
| 301 | fnLabel: string |
| 302 | selection: string |
| 303 | nodeSchema: z.ZodType<T> |
| 304 | }): Promise<T[]> { |
| 305 | const { prNumbers, githubToken, fnLabel, selection, nodeSchema } = args |
| 306 | if (prNumbers.length === 0) return [] |
| 307 | const aliases = prNumbers |
| 308 | .map( |
| 309 | number => ` |
| 310 | pr${number}: pullRequest(number: ${number}) { |
| 311 | ${selection} |
| 312 | }` |
| 313 | ) |
| 314 | .join('\n') |
| 315 | const query = `query { repository(owner: "47ng", name: "nuqs") { ${aliases} } }` |
| 316 | |
| 317 | const response = await fetch(`https://api.github.com/graphql?fn=${fnLabel}`, { |
| 318 | method: 'POST', |
| 319 | headers: { |
| 320 | Authorization: `Bearer ${githubToken}`, |
| 321 | 'Content-Type': 'application/json' |
| 322 | }, |
| 323 | body: JSON.stringify({ query }) |
| 324 | }) |
| 325 | if (!response.ok) { |
| 326 | throw new Error( |
| 327 | `${fnLabel}: GitHub API error ${response.status} ${response.statusText} for PRs [${prNumbers.join(', ')}]` |
| 328 | ) |
| 329 | } |
| 330 | const envelopeSchema = z.object({ |
| 331 | data: z.object({ |
| 332 | repository: z.record(z.string(), nodeSchema.nullable()) |
| 333 | }), |
| 334 | errors: z.array(z.object({ message: z.string() })).optional() |
| 335 | }) |
| 336 | const envelope = envelopeSchema.safeParse(await response.json()) |
| 337 | if (!envelope.success) { |
| 338 | throw new Error( |
| 339 | `${fnLabel}: unexpected GraphQL response shape for PRs [${prNumbers.join(', ')}]: ${envelope.error.message}` |
| 340 | ) |
| 341 | } |
| 342 | const { data, errors } = envelope.data |
| 343 | if (errors && errors.length > 0) { |
| 344 | // A null node here is a fetch failure, not a nonexistent PR — dropping it |
| 345 | // would silently lose a real change. Fail the release instead. |
| 346 | throw new Error( |
| 347 | `${fnLabel}: GraphQL errors for PRs [${prNumbers.join(', ')}]: ${errors.map(e => e.message).join('; ')}` |
| 348 | ) |
| 349 | } |
| 350 | const resolved = Object.values(data.repository).filter( |
| 351 | (node): node is T => node !== null |
| 352 | ) |
| 353 | if (resolved.length < prNumbers.length) { |
| 354 | const returned = new Set(resolved.map(node => node.number)) |
| 355 | const dropped = prNumbers.filter(number => !returned.has(number)) |
no outgoing calls
no test coverage detected