UNPKG

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