198 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			198 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | Object.defineProperty(exports, '__esModule', { value: true }); | ||
|  | 
 | ||
|  | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
|  | 
 | ||
|  | var deepmerge = _interopDefault(require('deepmerge')); | ||
|  | var isPlainObject = _interopDefault(require('is-plain-object')); | ||
|  | var urlTemplate = _interopDefault(require('url-template')); | ||
|  | var getUserAgent = _interopDefault(require('universal-user-agent')); | ||
|  | 
 | ||
|  | function lowercaseKeys(object) { | ||
|  |   if (!object) { | ||
|  |     return {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   return Object.keys(object).reduce((newObj, key) => { | ||
|  |     newObj[key.toLowerCase()] = object[key]; | ||
|  |     return newObj; | ||
|  |   }, {}); | ||
|  | } | ||
|  | 
 | ||
|  | function merge(defaults, route, options) { | ||
|  |   if (typeof route === "string") { | ||
|  |     let [method, url] = route.split(" "); | ||
|  |     options = Object.assign(url ? { | ||
|  |       method, | ||
|  |       url | ||
|  |     } : { | ||
|  |       url: method | ||
|  |     }, options); | ||
|  |   } else { | ||
|  |     options = route || {}; | ||
|  |   } // lowercase header names before merging with defaults to avoid duplicates
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   options.headers = lowercaseKeys(options.headers); | ||
|  |   const mergedOptions = deepmerge.all([defaults, options].filter(Boolean), { | ||
|  |     isMergeableObject: isPlainObject | ||
|  |   }); // mediaType.previews arrays are merged, instead of overwritten
 | ||
|  | 
 | ||
|  |   if (defaults && defaults.mediaType.previews.length) { | ||
|  |     mergedOptions.mediaType.previews = defaults.mediaType.previews.filter(preview => !mergedOptions.mediaType.previews.includes(preview)).concat(mergedOptions.mediaType.previews); | ||
|  |   } | ||
|  | 
 | ||
|  |   mergedOptions.mediaType.previews = mergedOptions.mediaType.previews.map(preview => preview.replace(/-preview/, "")); | ||
|  |   return mergedOptions; | ||
|  | } | ||
|  | 
 | ||
|  | function addQueryParameters(url, parameters) { | ||
|  |   const separator = /\?/.test(url) ? "&" : "?"; | ||
|  |   const names = Object.keys(parameters); | ||
|  | 
 | ||
|  |   if (names.length === 0) { | ||
|  |     return url; | ||
|  |   } | ||
|  | 
 | ||
|  |   return url + separator + names.map(name => { | ||
|  |     if (name === "q") { | ||
|  |       return "q=" + parameters.q.split("+").map(encodeURIComponent).join("+"); | ||
|  |     } | ||
|  | 
 | ||
|  |     return `${name}=${encodeURIComponent(parameters[name])}`; | ||
|  |   }).join("&"); | ||
|  | } | ||
|  | 
 | ||
|  | const urlVariableRegex = /\{[^}]+\}/g; | ||
|  | 
 | ||
|  | function removeNonChars(variableName) { | ||
|  |   return variableName.replace(/^\W+|\W+$/g, "").split(/,/); | ||
|  | } | ||
|  | 
 | ||
|  | function extractUrlVariableNames(url) { | ||
|  |   const matches = url.match(urlVariableRegex); | ||
|  | 
 | ||
|  |   if (!matches) { | ||
|  |     return []; | ||
|  |   } | ||
|  | 
 | ||
|  |   return matches.map(removeNonChars).reduce((a, b) => a.concat(b), []); | ||
|  | } | ||
|  | 
 | ||
|  | function omit(object, keysToOmit) { | ||
|  |   return Object.keys(object).filter(option => !keysToOmit.includes(option)).reduce((obj, key) => { | ||
|  |     obj[key] = object[key]; | ||
|  |     return obj; | ||
|  |   }, {}); | ||
|  | } | ||
|  | 
 | ||
|  | function parse(options) { | ||
|  |   // https://fetch.spec.whatwg.org/#methods
 | ||
|  |   let method = options.method.toUpperCase(); // replace :varname with {varname} to make it RFC 6570 compatible
 | ||
|  | 
 | ||
|  |   let url = options.url.replace(/:([a-z]\w+)/g, "{+$1}"); | ||
|  |   let headers = Object.assign({}, options.headers); | ||
|  |   let body; | ||
|  |   let parameters = omit(options, ["method", "baseUrl", "url", "headers", "request", "mediaType"]); // extract variable names from URL to calculate remaining variables later
 | ||
|  | 
 | ||
|  |   const urlVariableNames = extractUrlVariableNames(url); | ||
|  |   url = urlTemplate.parse(url).expand(parameters); | ||
|  | 
 | ||
|  |   if (!/^http/.test(url)) { | ||
|  |     url = options.baseUrl + url; | ||
|  |   } | ||
|  | 
 | ||
|  |   const omittedParameters = Object.keys(options).filter(option => urlVariableNames.includes(option)).concat("baseUrl"); | ||
|  |   const remainingParameters = omit(parameters, omittedParameters); | ||
|  |   const isBinaryRequset = /application\/octet-stream/i.test(headers.accept); | ||
|  | 
 | ||
|  |   if (!isBinaryRequset) { | ||
|  |     if (options.mediaType.format) { | ||
|  |       // e.g. application/vnd.github.v3+json => application/vnd.github.v3.raw
 | ||
|  |       headers.accept = headers.accept.split(/,/).map(preview => preview.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/, `application/vnd$1$2.${options.mediaType.format}`)).join(","); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (options.mediaType.previews.length) { | ||
|  |       const previewsFromAcceptHeader = headers.accept.match(/[\w-]+(?=-preview)/g) || []; | ||
|  |       headers.accept = previewsFromAcceptHeader.concat(options.mediaType.previews).map(preview => { | ||
|  |         const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json"; | ||
|  |         return `application/vnd.github.${preview}-preview${format}`; | ||
|  |       }).join(","); | ||
|  |     } | ||
|  |   } // for GET/HEAD requests, set URL query parameters from remaining parameters
 | ||
|  |   // for PATCH/POST/PUT/DELETE requests, set request body from remaining parameters
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   if (["GET", "HEAD"].includes(method)) { | ||
|  |     url = addQueryParameters(url, remainingParameters); | ||
|  |   } else { | ||
|  |     if ("data" in remainingParameters) { | ||
|  |       body = remainingParameters.data; | ||
|  |     } else { | ||
|  |       if (Object.keys(remainingParameters).length) { | ||
|  |         body = remainingParameters; | ||
|  |       } else { | ||
|  |         headers["content-length"] = 0; | ||
|  |       } | ||
|  |     } | ||
|  |   } // default content-type for JSON if body is set
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   if (!headers["content-type"] && typeof body !== "undefined") { | ||
|  |     headers["content-type"] = "application/json; charset=utf-8"; | ||
|  |   } // GitHub expects 'content-length: 0' header for PUT/PATCH requests without body.
 | ||
|  |   // fetch does not allow to set `content-length` header, but we can set body to an empty string
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") { | ||
|  |     body = ""; | ||
|  |   } // Only return body/request keys if present
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   return Object.assign({ | ||
|  |     method, | ||
|  |     url, | ||
|  |     headers | ||
|  |   }, typeof body !== "undefined" ? { | ||
|  |     body | ||
|  |   } : null, options.request ? { | ||
|  |     request: options.request | ||
|  |   } : null); | ||
|  | } | ||
|  | 
 | ||
|  | function endpointWithDefaults(defaults, route, options) { | ||
|  |   return parse(merge(defaults, route, options)); | ||
|  | } | ||
|  | 
 | ||
|  | function withDefaults(oldDefaults, newDefaults) { | ||
|  |   const DEFAULTS = merge(oldDefaults, newDefaults); | ||
|  |   const endpoint = endpointWithDefaults.bind(null, DEFAULTS); | ||
|  |   return Object.assign(endpoint, { | ||
|  |     DEFAULTS, | ||
|  |     defaults: withDefaults.bind(null, DEFAULTS), | ||
|  |     merge: merge.bind(null, DEFAULTS), | ||
|  |     parse | ||
|  |   }); | ||
|  | } | ||
|  | 
 | ||
|  | const VERSION = "0.0.0-development"; | ||
|  | 
 | ||
|  | const userAgent = `octokit-endpoint.js/${VERSION} ${getUserAgent()}`; | ||
|  | const DEFAULTS = { | ||
|  |   method: "GET", | ||
|  |   baseUrl: "https://api.github.com", | ||
|  |   headers: { | ||
|  |     accept: "application/vnd.github.v3+json", | ||
|  |     "user-agent": userAgent | ||
|  |   }, | ||
|  |   mediaType: { | ||
|  |     format: "", | ||
|  |     previews: [] | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | const endpoint = withDefaults(null, DEFAULTS); | ||
|  | 
 | ||
|  | exports.endpoint = endpoint; |