(users: string[], octokit: Octokit, signal: AbortSignal, url?: string)
| 291 | } |
| 292 | |
| 293 | const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: AbortSignal, url?: string) => { |
| 294 | const results = await Promise.allSettled(users.map((user) => githubQueryLimit(async () => { |
| 295 | try { |
| 296 | logger.debug(`Fetching repository info for user ${user}...`); |
| 297 | |
| 298 | const octokitToUse = await getOctokitWithGithubApp(octokit, user, url, `user ${user}`); |
| 299 | const { durationMs, data } = await measure(async () => { |
| 300 | const fetchFn = async () => { |
| 301 | let query = `user:${user}`; |
| 302 | // To include forks in the search results, we will need to add fork:true |
| 303 | // see: https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories |
| 304 | query += ' fork:true'; |
| 305 | // @note: We need to use GitHub's search API here since it is the only way |
| 306 | // to get all repositories (private and public) owned by a user that supports |
| 307 | // the username as a parameter. |
| 308 | // @see: https://github.com/orgs/community/discussions/24382#discussioncomment-3243958 |
| 309 | // @see: https://api.github.com/search/repositories?q=user:USERNAME |
| 310 | |
| 311 | // @note: We use paginate.iterator() instead of paginate() to check |
| 312 | // signal.aborted between pages. paginate() only passes the signal to |
| 313 | // individual fetch requests but doesn't check abort state between pages. |
| 314 | const allRepos: OctokitRepository[] = []; |
| 315 | const iterator = octokitToUse.paginate.iterator(octokitToUse.rest.search.repos, { |
| 316 | q: query, |
| 317 | per_page: 100, |
| 318 | request: { |
| 319 | signal, |
| 320 | }, |
| 321 | }); |
| 322 | |
| 323 | for await (const { data: repos } of iterator) { |
| 324 | if (signal.aborted) { |
| 325 | throw new DOMException('Operation aborted', 'AbortError'); |
| 326 | } |
| 327 | allRepos.push(...(repos as OctokitRepository[])); |
| 328 | } |
| 329 | |
| 330 | return allRepos; |
| 331 | }; |
| 332 | |
| 333 | return fetchWithRetry(fetchFn, `user ${user}`, logger); |
| 334 | }); |
| 335 | |
| 336 | logger.debug(`Found ${data.length} owned by user ${user} in ${durationMs}ms.`); |
| 337 | return { |
| 338 | type: 'valid' as const, |
| 339 | data |
| 340 | }; |
| 341 | } catch (error) { |
| 342 | Sentry.captureException(error); |
| 343 | logger.error(`Failed to fetch repositories for user ${user}.`, error); |
| 344 | |
| 345 | if (isHttpError(error, 404)) { |
| 346 | const warning = `User ${user} not found or no access`; |
| 347 | logger.warn(warning); |
| 348 | return { |
| 349 | type: 'warning' as const, |
| 350 | warning |
no test coverage detected