index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. const VERSION = "1.1.2";
  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. * - https://developer.github.com/v3/search/#example (key `items`)
  10. * - https://developer.github.com/v3/checks/runs/#response-3 (key: `check_runs`)
  11. * - https://developer.github.com/v3/checks/suites/#response-1 (key: `check_suites`)
  12. * - https://developer.github.com/v3/apps/installations/#list-repositories (key: `repositories`)
  13. * - https://developer.github.com/v3/apps/installations/#list-installations-for-a-user (key `installations`)
  14. *
  15. * Octokit normalizes these responses so that paginated results are always returned following
  16. * the same structure. One challenge is that if the list response has only one page, no Link
  17. * header is provided, so this header alone is not sufficient to check wether a response is
  18. * paginated or not. For the exceptions with the namespace, a fallback check for the route
  19. * paths has to be added in order to normalize the response. We cannot check for the total_count
  20. * property because it also exists in the response of Get the combined status for a specific ref.
  21. */
  22. const REGEX = [
  23. /^\/search\//,
  24. /^\/repos\/[^/]+\/[^/]+\/commits\/[^/]+\/(check-runs|check-suites)([^/]|$)/,
  25. /^\/installation\/repositories([^/]|$)/,
  26. /^\/user\/installations([^/]|$)/,
  27. /^\/repos\/[^/]+\/[^/]+\/actions\/secrets([^/]|$)/,
  28. /^\/repos\/[^/]+\/[^/]+\/actions\/workflows(\/[^/]+\/runs)?([^/]|$)/,
  29. /^\/repos\/[^/]+\/[^/]+\/actions\/runs(\/[^/]+\/(artifacts|jobs))?([^/]|$)/
  30. ];
  31. function normalizePaginatedListResponse(octokit, url, response) {
  32. const path = url.replace(octokit.request.endpoint.DEFAULTS.baseUrl, "");
  33. const responseNeedsNormalization = REGEX.find(regex => regex.test(path));
  34. if (!responseNeedsNormalization)
  35. return;
  36. // keep the additional properties intact as there is currently no other way
  37. // to retrieve the same information.
  38. const incompleteResults = response.data.incomplete_results;
  39. const repositorySelection = response.data.repository_selection;
  40. const totalCount = response.data.total_count;
  41. delete response.data.incomplete_results;
  42. delete response.data.repository_selection;
  43. delete response.data.total_count;
  44. const namespaceKey = Object.keys(response.data)[0];
  45. const data = response.data[namespaceKey];
  46. response.data = data;
  47. if (typeof incompleteResults !== "undefined") {
  48. response.data.incomplete_results = incompleteResults;
  49. }
  50. if (typeof repositorySelection !== "undefined") {
  51. response.data.repository_selection = repositorySelection;
  52. }
  53. response.data.total_count = totalCount;
  54. Object.defineProperty(response.data, namespaceKey, {
  55. get() {
  56. octokit.log.warn(`[@octokit/paginate-rest] "response.data.${namespaceKey}" is deprecated for "GET ${path}". Get the results directly from "response.data"`);
  57. return Array.from(data);
  58. }
  59. });
  60. }
  61. function iterator(octokit, route, parameters) {
  62. const options = octokit.request.endpoint(route, parameters);
  63. const method = options.method;
  64. const headers = options.headers;
  65. let url = options.url;
  66. return {
  67. [Symbol.asyncIterator]: () => ({
  68. next() {
  69. if (!url) {
  70. return Promise.resolve({ done: true });
  71. }
  72. return octokit
  73. .request({ method, url, headers })
  74. .then((response) => {
  75. normalizePaginatedListResponse(octokit, url, response);
  76. // `response.headers.link` format:
  77. // '<https://api.github.com/users/aseemk/followers?page=2>; rel="next", <https://api.github.com/users/aseemk/followers?page=2>; rel="last"'
  78. // sets `url` to undefined if "next" URL is not present or `link` header is not set
  79. url = ((response.headers.link || "").match(/<([^>]+)>;\s*rel="next"/) || [])[1];
  80. return { value: response };
  81. });
  82. }
  83. })
  84. };
  85. }
  86. function paginate(octokit, route, parameters, mapFn) {
  87. if (typeof parameters === "function") {
  88. mapFn = parameters;
  89. parameters = undefined;
  90. }
  91. return gather(octokit, [], iterator(octokit, route, parameters)[Symbol.asyncIterator](), mapFn);
  92. }
  93. function gather(octokit, results, iterator, mapFn) {
  94. return iterator.next().then(result => {
  95. if (result.done) {
  96. return results;
  97. }
  98. let earlyExit = false;
  99. function done() {
  100. earlyExit = true;
  101. }
  102. results = results.concat(mapFn ? mapFn(result.value, done) : result.value.data);
  103. if (earlyExit) {
  104. return results;
  105. }
  106. return gather(octokit, results, iterator, mapFn);
  107. });
  108. }
  109. /**
  110. * @param octokit Octokit instance
  111. * @param options Options passed to Octokit constructor
  112. */
  113. function paginateRest(octokit) {
  114. return {
  115. paginate: Object.assign(paginate.bind(null, octokit), {
  116. iterator: iterator.bind(null, octokit)
  117. })
  118. };
  119. }
  120. paginateRest.VERSION = VERSION;
  121. export { paginateRest };
  122. //# sourceMappingURL=index.js.map