index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import { isPlainObject } from 'is-plain-object';
  2. import { getUserAgent } from 'universal-user-agent';
  3. function lowercaseKeys(object) {
  4. if (!object) {
  5. return {};
  6. }
  7. return Object.keys(object).reduce((newObj, key) => {
  8. newObj[key.toLowerCase()] = object[key];
  9. return newObj;
  10. }, {});
  11. }
  12. function mergeDeep(defaults, options) {
  13. const result = Object.assign({}, defaults);
  14. Object.keys(options).forEach((key) => {
  15. if (isPlainObject(options[key])) {
  16. if (!(key in defaults))
  17. Object.assign(result, { [key]: options[key] });
  18. else
  19. result[key] = mergeDeep(defaults[key], options[key]);
  20. }
  21. else {
  22. Object.assign(result, { [key]: options[key] });
  23. }
  24. });
  25. return result;
  26. }
  27. function removeUndefinedProperties(obj) {
  28. for (const key in obj) {
  29. if (obj[key] === undefined) {
  30. delete obj[key];
  31. }
  32. }
  33. return obj;
  34. }
  35. function merge(defaults, route, options) {
  36. if (typeof route === "string") {
  37. let [method, url] = route.split(" ");
  38. options = Object.assign(url ? { method, url } : { url: method }, options);
  39. }
  40. else {
  41. options = Object.assign({}, route);
  42. }
  43. // lowercase header names before merging with defaults to avoid duplicates
  44. options.headers = lowercaseKeys(options.headers);
  45. // remove properties with undefined values before merging
  46. removeUndefinedProperties(options);
  47. removeUndefinedProperties(options.headers);
  48. const mergedOptions = mergeDeep(defaults || {}, options);
  49. // mediaType.previews arrays are merged, instead of overwritten
  50. if (defaults && defaults.mediaType.previews.length) {
  51. mergedOptions.mediaType.previews = defaults.mediaType.previews
  52. .filter((preview) => !mergedOptions.mediaType.previews.includes(preview))
  53. .concat(mergedOptions.mediaType.previews);
  54. }
  55. mergedOptions.mediaType.previews = mergedOptions.mediaType.previews.map((preview) => preview.replace(/-preview/, ""));
  56. return mergedOptions;
  57. }
  58. function addQueryParameters(url, parameters) {
  59. const separator = /\?/.test(url) ? "&" : "?";
  60. const names = Object.keys(parameters);
  61. if (names.length === 0) {
  62. return url;
  63. }
  64. return (url +
  65. separator +
  66. names
  67. .map((name) => {
  68. if (name === "q") {
  69. return ("q=" + parameters.q.split("+").map(encodeURIComponent).join("+"));
  70. }
  71. return `${name}=${encodeURIComponent(parameters[name])}`;
  72. })
  73. .join("&"));
  74. }
  75. const urlVariableRegex = /\{[^}]+\}/g;
  76. function removeNonChars(variableName) {
  77. return variableName.replace(/^\W+|\W+$/g, "").split(/,/);
  78. }
  79. function extractUrlVariableNames(url) {
  80. const matches = url.match(urlVariableRegex);
  81. if (!matches) {
  82. return [];
  83. }
  84. return matches.map(removeNonChars).reduce((a, b) => a.concat(b), []);
  85. }
  86. function omit(object, keysToOmit) {
  87. return Object.keys(object)
  88. .filter((option) => !keysToOmit.includes(option))
  89. .reduce((obj, key) => {
  90. obj[key] = object[key];
  91. return obj;
  92. }, {});
  93. }
  94. // Based on https://github.com/bramstein/url-template, licensed under BSD
  95. // TODO: create separate package.
  96. //
  97. // Copyright (c) 2012-2014, Bram Stein
  98. // All rights reserved.
  99. // Redistribution and use in source and binary forms, with or without
  100. // modification, are permitted provided that the following conditions
  101. // are met:
  102. // 1. Redistributions of source code must retain the above copyright
  103. // notice, this list of conditions and the following disclaimer.
  104. // 2. Redistributions in binary form must reproduce the above copyright
  105. // notice, this list of conditions and the following disclaimer in the
  106. // documentation and/or other materials provided with the distribution.
  107. // 3. The name of the author may not be used to endorse or promote products
  108. // derived from this software without specific prior written permission.
  109. // THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
  110. // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  111. // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  112. // EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  113. // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  114. // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  115. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  116. // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  117. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  118. // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  119. /* istanbul ignore file */
  120. function encodeReserved(str) {
  121. return str
  122. .split(/(%[0-9A-Fa-f]{2})/g)
  123. .map(function (part) {
  124. if (!/%[0-9A-Fa-f]/.test(part)) {
  125. part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]");
  126. }
  127. return part;
  128. })
  129. .join("");
  130. }
  131. function encodeUnreserved(str) {
  132. return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
  133. return "%" + c.charCodeAt(0).toString(16).toUpperCase();
  134. });
  135. }
  136. function encodeValue(operator, value, key) {
  137. value =
  138. operator === "+" || operator === "#"
  139. ? encodeReserved(value)
  140. : encodeUnreserved(value);
  141. if (key) {
  142. return encodeUnreserved(key) + "=" + value;
  143. }
  144. else {
  145. return value;
  146. }
  147. }
  148. function isDefined(value) {
  149. return value !== undefined && value !== null;
  150. }
  151. function isKeyOperator(operator) {
  152. return operator === ";" || operator === "&" || operator === "?";
  153. }
  154. function getValues(context, operator, key, modifier) {
  155. var value = context[key], result = [];
  156. if (isDefined(value) && value !== "") {
  157. if (typeof value === "string" ||
  158. typeof value === "number" ||
  159. typeof value === "boolean") {
  160. value = value.toString();
  161. if (modifier && modifier !== "*") {
  162. value = value.substring(0, parseInt(modifier, 10));
  163. }
  164. result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
  165. }
  166. else {
  167. if (modifier === "*") {
  168. if (Array.isArray(value)) {
  169. value.filter(isDefined).forEach(function (value) {
  170. result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
  171. });
  172. }
  173. else {
  174. Object.keys(value).forEach(function (k) {
  175. if (isDefined(value[k])) {
  176. result.push(encodeValue(operator, value[k], k));
  177. }
  178. });
  179. }
  180. }
  181. else {
  182. const tmp = [];
  183. if (Array.isArray(value)) {
  184. value.filter(isDefined).forEach(function (value) {
  185. tmp.push(encodeValue(operator, value));
  186. });
  187. }
  188. else {
  189. Object.keys(value).forEach(function (k) {
  190. if (isDefined(value[k])) {
  191. tmp.push(encodeUnreserved(k));
  192. tmp.push(encodeValue(operator, value[k].toString()));
  193. }
  194. });
  195. }
  196. if (isKeyOperator(operator)) {
  197. result.push(encodeUnreserved(key) + "=" + tmp.join(","));
  198. }
  199. else if (tmp.length !== 0) {
  200. result.push(tmp.join(","));
  201. }
  202. }
  203. }
  204. }
  205. else {
  206. if (operator === ";") {
  207. if (isDefined(value)) {
  208. result.push(encodeUnreserved(key));
  209. }
  210. }
  211. else if (value === "" && (operator === "&" || operator === "?")) {
  212. result.push(encodeUnreserved(key) + "=");
  213. }
  214. else if (value === "") {
  215. result.push("");
  216. }
  217. }
  218. return result;
  219. }
  220. function parseUrl(template) {
  221. return {
  222. expand: expand.bind(null, template),
  223. };
  224. }
  225. function expand(template, context) {
  226. var operators = ["+", "#", ".", "/", ";", "?", "&"];
  227. return template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function (_, expression, literal) {
  228. if (expression) {
  229. let operator = "";
  230. const values = [];
  231. if (operators.indexOf(expression.charAt(0)) !== -1) {
  232. operator = expression.charAt(0);
  233. expression = expression.substr(1);
  234. }
  235. expression.split(/,/g).forEach(function (variable) {
  236. var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable);
  237. values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3]));
  238. });
  239. if (operator && operator !== "+") {
  240. var separator = ",";
  241. if (operator === "?") {
  242. separator = "&";
  243. }
  244. else if (operator !== "#") {
  245. separator = operator;
  246. }
  247. return (values.length !== 0 ? operator : "") + values.join(separator);
  248. }
  249. else {
  250. return values.join(",");
  251. }
  252. }
  253. else {
  254. return encodeReserved(literal);
  255. }
  256. });
  257. }
  258. function parse(options) {
  259. // https://fetch.spec.whatwg.org/#methods
  260. let method = options.method.toUpperCase();
  261. // replace :varname with {varname} to make it RFC 6570 compatible
  262. let url = (options.url || "/").replace(/:([a-z]\w+)/g, "{$1}");
  263. let headers = Object.assign({}, options.headers);
  264. let body;
  265. let parameters = omit(options, [
  266. "method",
  267. "baseUrl",
  268. "url",
  269. "headers",
  270. "request",
  271. "mediaType",
  272. ]);
  273. // extract variable names from URL to calculate remaining variables later
  274. const urlVariableNames = extractUrlVariableNames(url);
  275. url = parseUrl(url).expand(parameters);
  276. if (!/^http/.test(url)) {
  277. url = options.baseUrl + url;
  278. }
  279. const omittedParameters = Object.keys(options)
  280. .filter((option) => urlVariableNames.includes(option))
  281. .concat("baseUrl");
  282. const remainingParameters = omit(parameters, omittedParameters);
  283. const isBinaryRequest = /application\/octet-stream/i.test(headers.accept);
  284. if (!isBinaryRequest) {
  285. if (options.mediaType.format) {
  286. // e.g. application/vnd.github.v3+json => application/vnd.github.v3.raw
  287. headers.accept = headers.accept
  288. .split(/,/)
  289. .map((preview) => preview.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/, `application/vnd$1$2.${options.mediaType.format}`))
  290. .join(",");
  291. }
  292. if (options.mediaType.previews.length) {
  293. const previewsFromAcceptHeader = headers.accept.match(/[\w-]+(?=-preview)/g) || [];
  294. headers.accept = previewsFromAcceptHeader
  295. .concat(options.mediaType.previews)
  296. .map((preview) => {
  297. const format = options.mediaType.format
  298. ? `.${options.mediaType.format}`
  299. : "+json";
  300. return `application/vnd.github.${preview}-preview${format}`;
  301. })
  302. .join(",");
  303. }
  304. }
  305. // for GET/HEAD requests, set URL query parameters from remaining parameters
  306. // for PATCH/POST/PUT/DELETE requests, set request body from remaining parameters
  307. if (["GET", "HEAD"].includes(method)) {
  308. url = addQueryParameters(url, remainingParameters);
  309. }
  310. else {
  311. if ("data" in remainingParameters) {
  312. body = remainingParameters.data;
  313. }
  314. else {
  315. if (Object.keys(remainingParameters).length) {
  316. body = remainingParameters;
  317. }
  318. else {
  319. headers["content-length"] = 0;
  320. }
  321. }
  322. }
  323. // default content-type for JSON if body is set
  324. if (!headers["content-type"] && typeof body !== "undefined") {
  325. headers["content-type"] = "application/json; charset=utf-8";
  326. }
  327. // GitHub expects 'content-length: 0' header for PUT/PATCH requests without body.
  328. // fetch does not allow to set `content-length` header, but we can set body to an empty string
  329. if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") {
  330. body = "";
  331. }
  332. // Only return body/request keys if present
  333. return Object.assign({ method, url, headers }, typeof body !== "undefined" ? { body } : null, options.request ? { request: options.request } : null);
  334. }
  335. function endpointWithDefaults(defaults, route, options) {
  336. return parse(merge(defaults, route, options));
  337. }
  338. function withDefaults(oldDefaults, newDefaults) {
  339. const DEFAULTS = merge(oldDefaults, newDefaults);
  340. const endpoint = endpointWithDefaults.bind(null, DEFAULTS);
  341. return Object.assign(endpoint, {
  342. DEFAULTS,
  343. defaults: withDefaults.bind(null, DEFAULTS),
  344. merge: merge.bind(null, DEFAULTS),
  345. parse,
  346. });
  347. }
  348. const VERSION = "6.0.12";
  349. const userAgent = `octokit-endpoint.js/${VERSION} ${getUserAgent()}`;
  350. // DEFAULTS has all properties set that EndpointOptions has, except url.
  351. // So we use RequestParameters and add method as additional required property.
  352. const DEFAULTS = {
  353. method: "GET",
  354. baseUrl: "https://api.github.com",
  355. headers: {
  356. accept: "application/vnd.github.v3+json",
  357. "user-agent": userAgent,
  358. },
  359. mediaType: {
  360. format: "",
  361. previews: [],
  362. },
  363. };
  364. const endpoint = withDefaults(null, DEFAULTS);
  365. export { endpoint };
  366. //# sourceMappingURL=index.js.map