index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. const VERSION = "2.17.0";
  2. /**
  3. * Some “list” response that can be paginated have a different response structure
  4. *
  5. * They have a `total_count` key in the response (search also has `incomplete_results`,
  6. * /installation/repositories also has `repository_selection`), as well as a key with
  7. * the list of the items which name varies from endpoint to endpoint.
  8. *
  9. * Octokit normalizes these responses so that paginated results are always returned following
  10. * the same structure. One challenge is that if the list response has only one page, no Link
  11. * header is provided, so this header alone is not sufficient to check wether a response is
  12. * paginated or not.
  13. *
  14. * We check if a "total_count" key is present in the response data, but also make sure that
  15. * a "url" property is not, as the "Get the combined status for a specific ref" endpoint would
  16. * otherwise match: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
  17. */
  18. function normalizePaginatedListResponse(response) {
  19. // endpoints can respond with 204 if repository is empty
  20. if (!response.data) {
  21. return {
  22. ...response,
  23. data: [],
  24. };
  25. }
  26. const responseNeedsNormalization = "total_count" in response.data && !("url" in response.data);
  27. if (!responseNeedsNormalization)
  28. return response;
  29. // keep the additional properties intact as there is currently no other way
  30. // to retrieve the same information.
  31. const incompleteResults = response.data.incomplete_results;
  32. const repositorySelection = response.data.repository_selection;
  33. const totalCount = response.data.total_count;
  34. delete response.data.incomplete_results;
  35. delete response.data.repository_selection;
  36. delete response.data.total_count;
  37. const namespaceKey = Object.keys(response.data)[0];
  38. const data = response.data[namespaceKey];
  39. response.data = data;
  40. if (typeof incompleteResults !== "undefined") {
  41. response.data.incomplete_results = incompleteResults;
  42. }
  43. if (typeof repositorySelection !== "undefined") {
  44. response.data.repository_selection = repositorySelection;
  45. }
  46. response.data.total_count = totalCount;
  47. return response;
  48. }
  49. function iterator(octokit, route, parameters) {
  50. const options = typeof route === "function"
  51. ? route.endpoint(parameters)
  52. : octokit.request.endpoint(route, parameters);
  53. const requestMethod = typeof route === "function" ? route : octokit.request;
  54. const method = options.method;
  55. const headers = options.headers;
  56. let url = options.url;
  57. return {
  58. [Symbol.asyncIterator]: () => ({
  59. async next() {
  60. if (!url)
  61. return { done: true };
  62. try {
  63. const response = await requestMethod({ method, url, headers });
  64. const normalizedResponse = normalizePaginatedListResponse(response);
  65. // `response.headers.link` format:
  66. // '<https://api.github.com/users/aseemk/followers?page=2>; rel="next", <https://api.github.com/users/aseemk/followers?page=2>; rel="last"'
  67. // sets `url` to undefined if "next" URL is not present or `link` header is not set
  68. url = ((normalizedResponse.headers.link || "").match(/<([^>]+)>;\s*rel="next"/) || [])[1];
  69. return { value: normalizedResponse };
  70. }
  71. catch (error) {
  72. if (error.status !== 409)
  73. throw error;
  74. url = "";
  75. return {
  76. value: {
  77. status: 200,
  78. headers: {},
  79. data: [],
  80. },
  81. };
  82. }
  83. },
  84. }),
  85. };
  86. }
  87. function paginate(octokit, route, parameters, mapFn) {
  88. if (typeof parameters === "function") {
  89. mapFn = parameters;
  90. parameters = undefined;
  91. }
  92. return gather(octokit, [], iterator(octokit, route, parameters)[Symbol.asyncIterator](), mapFn);
  93. }
  94. function gather(octokit, results, iterator, mapFn) {
  95. return iterator.next().then((result) => {
  96. if (result.done) {
  97. return results;
  98. }
  99. let earlyExit = false;
  100. function done() {
  101. earlyExit = true;
  102. }
  103. results = results.concat(mapFn ? mapFn(result.value, done) : result.value.data);
  104. if (earlyExit) {
  105. return results;
  106. }
  107. return gather(octokit, results, iterator, mapFn);
  108. });
  109. }
  110. const composePaginateRest = Object.assign(paginate, {
  111. iterator,
  112. });
  113. const paginatingEndpoints = [
  114. "GET /app/hook/deliveries",
  115. "GET /app/installations",
  116. "GET /applications/grants",
  117. "GET /authorizations",
  118. "GET /enterprises/{enterprise}/actions/permissions/organizations",
  119. "GET /enterprises/{enterprise}/actions/runner-groups",
  120. "GET /enterprises/{enterprise}/actions/runner-groups/{runner_group_id}/organizations",
  121. "GET /enterprises/{enterprise}/actions/runner-groups/{runner_group_id}/runners",
  122. "GET /enterprises/{enterprise}/actions/runners",
  123. "GET /enterprises/{enterprise}/actions/runners/downloads",
  124. "GET /events",
  125. "GET /gists",
  126. "GET /gists/public",
  127. "GET /gists/starred",
  128. "GET /gists/{gist_id}/comments",
  129. "GET /gists/{gist_id}/commits",
  130. "GET /gists/{gist_id}/forks",
  131. "GET /installation/repositories",
  132. "GET /issues",
  133. "GET /marketplace_listing/plans",
  134. "GET /marketplace_listing/plans/{plan_id}/accounts",
  135. "GET /marketplace_listing/stubbed/plans",
  136. "GET /marketplace_listing/stubbed/plans/{plan_id}/accounts",
  137. "GET /networks/{owner}/{repo}/events",
  138. "GET /notifications",
  139. "GET /organizations",
  140. "GET /orgs/{org}/actions/permissions/repositories",
  141. "GET /orgs/{org}/actions/runner-groups",
  142. "GET /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories",
  143. "GET /orgs/{org}/actions/runner-groups/{runner_group_id}/runners",
  144. "GET /orgs/{org}/actions/runners",
  145. "GET /orgs/{org}/actions/runners/downloads",
  146. "GET /orgs/{org}/actions/secrets",
  147. "GET /orgs/{org}/actions/secrets/{secret_name}/repositories",
  148. "GET /orgs/{org}/blocks",
  149. "GET /orgs/{org}/credential-authorizations",
  150. "GET /orgs/{org}/events",
  151. "GET /orgs/{org}/failed_invitations",
  152. "GET /orgs/{org}/hooks",
  153. "GET /orgs/{org}/hooks/{hook_id}/deliveries",
  154. "GET /orgs/{org}/installations",
  155. "GET /orgs/{org}/invitations",
  156. "GET /orgs/{org}/invitations/{invitation_id}/teams",
  157. "GET /orgs/{org}/issues",
  158. "GET /orgs/{org}/members",
  159. "GET /orgs/{org}/migrations",
  160. "GET /orgs/{org}/migrations/{migration_id}/repositories",
  161. "GET /orgs/{org}/outside_collaborators",
  162. "GET /orgs/{org}/packages",
  163. "GET /orgs/{org}/projects",
  164. "GET /orgs/{org}/public_members",
  165. "GET /orgs/{org}/repos",
  166. "GET /orgs/{org}/secret-scanning/alerts",
  167. "GET /orgs/{org}/team-sync/groups",
  168. "GET /orgs/{org}/teams",
  169. "GET /orgs/{org}/teams/{team_slug}/discussions",
  170. "GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments",
  171. "GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions",
  172. "GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions",
  173. "GET /orgs/{org}/teams/{team_slug}/invitations",
  174. "GET /orgs/{org}/teams/{team_slug}/members",
  175. "GET /orgs/{org}/teams/{team_slug}/projects",
  176. "GET /orgs/{org}/teams/{team_slug}/repos",
  177. "GET /orgs/{org}/teams/{team_slug}/team-sync/group-mappings",
  178. "GET /orgs/{org}/teams/{team_slug}/teams",
  179. "GET /projects/columns/{column_id}/cards",
  180. "GET /projects/{project_id}/collaborators",
  181. "GET /projects/{project_id}/columns",
  182. "GET /repos/{owner}/{repo}/actions/artifacts",
  183. "GET /repos/{owner}/{repo}/actions/runners",
  184. "GET /repos/{owner}/{repo}/actions/runners/downloads",
  185. "GET /repos/{owner}/{repo}/actions/runs",
  186. "GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts",
  187. "GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs",
  188. "GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs",
  189. "GET /repos/{owner}/{repo}/actions/secrets",
  190. "GET /repos/{owner}/{repo}/actions/workflows",
  191. "GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs",
  192. "GET /repos/{owner}/{repo}/assignees",
  193. "GET /repos/{owner}/{repo}/autolinks",
  194. "GET /repos/{owner}/{repo}/branches",
  195. "GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations",
  196. "GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs",
  197. "GET /repos/{owner}/{repo}/code-scanning/alerts",
  198. "GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",
  199. "GET /repos/{owner}/{repo}/code-scanning/analyses",
  200. "GET /repos/{owner}/{repo}/collaborators",
  201. "GET /repos/{owner}/{repo}/comments",
  202. "GET /repos/{owner}/{repo}/comments/{comment_id}/reactions",
  203. "GET /repos/{owner}/{repo}/commits",
  204. "GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head",
  205. "GET /repos/{owner}/{repo}/commits/{commit_sha}/comments",
  206. "GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls",
  207. "GET /repos/{owner}/{repo}/commits/{ref}/check-runs",
  208. "GET /repos/{owner}/{repo}/commits/{ref}/check-suites",
  209. "GET /repos/{owner}/{repo}/commits/{ref}/statuses",
  210. "GET /repos/{owner}/{repo}/contributors",
  211. "GET /repos/{owner}/{repo}/deployments",
  212. "GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses",
  213. "GET /repos/{owner}/{repo}/events",
  214. "GET /repos/{owner}/{repo}/forks",
  215. "GET /repos/{owner}/{repo}/git/matching-refs/{ref}",
  216. "GET /repos/{owner}/{repo}/hooks",
  217. "GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries",
  218. "GET /repos/{owner}/{repo}/invitations",
  219. "GET /repos/{owner}/{repo}/issues",
  220. "GET /repos/{owner}/{repo}/issues/comments",
  221. "GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions",
  222. "GET /repos/{owner}/{repo}/issues/events",
  223. "GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
  224. "GET /repos/{owner}/{repo}/issues/{issue_number}/events",
  225. "GET /repos/{owner}/{repo}/issues/{issue_number}/labels",
  226. "GET /repos/{owner}/{repo}/issues/{issue_number}/reactions",
  227. "GET /repos/{owner}/{repo}/issues/{issue_number}/timeline",
  228. "GET /repos/{owner}/{repo}/keys",
  229. "GET /repos/{owner}/{repo}/labels",
  230. "GET /repos/{owner}/{repo}/milestones",
  231. "GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels",
  232. "GET /repos/{owner}/{repo}/notifications",
  233. "GET /repos/{owner}/{repo}/pages/builds",
  234. "GET /repos/{owner}/{repo}/projects",
  235. "GET /repos/{owner}/{repo}/pulls",
  236. "GET /repos/{owner}/{repo}/pulls/comments",
  237. "GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions",
  238. "GET /repos/{owner}/{repo}/pulls/{pull_number}/comments",
  239. "GET /repos/{owner}/{repo}/pulls/{pull_number}/commits",
  240. "GET /repos/{owner}/{repo}/pulls/{pull_number}/files",
  241. "GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers",
  242. "GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews",
  243. "GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments",
  244. "GET /repos/{owner}/{repo}/releases",
  245. "GET /repos/{owner}/{repo}/releases/{release_id}/assets",
  246. "GET /repos/{owner}/{repo}/secret-scanning/alerts",
  247. "GET /repos/{owner}/{repo}/stargazers",
  248. "GET /repos/{owner}/{repo}/subscribers",
  249. "GET /repos/{owner}/{repo}/tags",
  250. "GET /repos/{owner}/{repo}/teams",
  251. "GET /repositories",
  252. "GET /repositories/{repository_id}/environments/{environment_name}/secrets",
  253. "GET /scim/v2/enterprises/{enterprise}/Groups",
  254. "GET /scim/v2/enterprises/{enterprise}/Users",
  255. "GET /scim/v2/organizations/{org}/Users",
  256. "GET /search/code",
  257. "GET /search/commits",
  258. "GET /search/issues",
  259. "GET /search/labels",
  260. "GET /search/repositories",
  261. "GET /search/topics",
  262. "GET /search/users",
  263. "GET /teams/{team_id}/discussions",
  264. "GET /teams/{team_id}/discussions/{discussion_number}/comments",
  265. "GET /teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions",
  266. "GET /teams/{team_id}/discussions/{discussion_number}/reactions",
  267. "GET /teams/{team_id}/invitations",
  268. "GET /teams/{team_id}/members",
  269. "GET /teams/{team_id}/projects",
  270. "GET /teams/{team_id}/repos",
  271. "GET /teams/{team_id}/team-sync/group-mappings",
  272. "GET /teams/{team_id}/teams",
  273. "GET /user/blocks",
  274. "GET /user/emails",
  275. "GET /user/followers",
  276. "GET /user/following",
  277. "GET /user/gpg_keys",
  278. "GET /user/installations",
  279. "GET /user/installations/{installation_id}/repositories",
  280. "GET /user/issues",
  281. "GET /user/keys",
  282. "GET /user/marketplace_purchases",
  283. "GET /user/marketplace_purchases/stubbed",
  284. "GET /user/memberships/orgs",
  285. "GET /user/migrations",
  286. "GET /user/migrations/{migration_id}/repositories",
  287. "GET /user/orgs",
  288. "GET /user/packages",
  289. "GET /user/public_emails",
  290. "GET /user/repos",
  291. "GET /user/repository_invitations",
  292. "GET /user/starred",
  293. "GET /user/subscriptions",
  294. "GET /user/teams",
  295. "GET /users",
  296. "GET /users/{username}/events",
  297. "GET /users/{username}/events/orgs/{org}",
  298. "GET /users/{username}/events/public",
  299. "GET /users/{username}/followers",
  300. "GET /users/{username}/following",
  301. "GET /users/{username}/gists",
  302. "GET /users/{username}/gpg_keys",
  303. "GET /users/{username}/keys",
  304. "GET /users/{username}/orgs",
  305. "GET /users/{username}/packages",
  306. "GET /users/{username}/projects",
  307. "GET /users/{username}/received_events",
  308. "GET /users/{username}/received_events/public",
  309. "GET /users/{username}/repos",
  310. "GET /users/{username}/starred",
  311. "GET /users/{username}/subscriptions",
  312. ];
  313. function isPaginatingEndpoint(arg) {
  314. if (typeof arg === "string") {
  315. return paginatingEndpoints.includes(arg);
  316. }
  317. else {
  318. return false;
  319. }
  320. }
  321. /**
  322. * @param octokit Octokit instance
  323. * @param options Options passed to Octokit constructor
  324. */
  325. function paginateRest(octokit) {
  326. return {
  327. paginate: Object.assign(paginate.bind(null, octokit), {
  328. iterator: iterator.bind(null, octokit),
  329. }),
  330. };
  331. }
  332. paginateRest.VERSION = VERSION;
  333. export { composePaginateRest, isPaginatingEndpoint, paginateRest, paginatingEndpoints };
  334. //# sourceMappingURL=index.js.map