11import { isPlainObject } from "./is-plain-object.js" ;
22import { RequestError } from "@octokit/request-error" ;
3- import type { EndpointInterface } from "@octokit/types" ;
3+ import type { EndpointInterface , OctokitResponse } from "@octokit/types" ;
44
5- export default function fetchWrapper (
5+ export default async function fetchWrapper (
66 requestOptions : ReturnType < EndpointInterface > ,
7- ) {
8- const log =
9- requestOptions . request && requestOptions . request . log
10- ? requestOptions . request . log
11- : console ;
12- const parseSuccessResponseBody =
13- requestOptions . request ?. parseSuccessResponseBody !== false ;
14-
15- if (
16- isPlainObject ( requestOptions . body ) ||
17- Array . isArray ( requestOptions . body )
18- ) {
19- requestOptions . body = JSON . stringify ( requestOptions . body ) ;
20- }
21-
22- let headers : { [ header : string ] : string } = { } ;
23- let status : number ;
24- let url : string ;
25-
7+ ) : Promise < OctokitResponse < any > > {
268 const fetch : typeof globalThis . fetch =
279 requestOptions . request ?. fetch || globalThis . fetch ;
2810
@@ -32,109 +14,46 @@ export default function fetchWrapper(
3214 ) ;
3315 }
3416
35- return fetch ( requestOptions . url , {
36- method : requestOptions . method ,
37- body : requestOptions . body ,
38- redirect : requestOptions . request ?. redirect ,
39- // Header values must be `string`
40- headers : Object . fromEntries (
41- Object . entries ( requestOptions . headers ) . map ( ( [ name , value ] ) => [
42- name ,
43- String ( value ) ,
44- ] ) ,
45- ) ,
46- signal : requestOptions . request ?. signal ,
47- // duplex must be set if request.body is ReadableStream or Async Iterables.
48- // See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
49- ...( requestOptions . body && { duplex : "half" } ) ,
50- } )
51- . then ( async ( response ) => {
52- url = response . url ;
53- status = response . status ;
54-
55- for ( const keyAndValue of response . headers ) {
56- headers [ keyAndValue [ 0 ] ] = keyAndValue [ 1 ] ;
57- }
58-
59- if ( "deprecation" in headers ) {
60- const matches =
61- headers . link && headers . link . match ( / < ( [ ^ > ] + ) > ; r e l = " d e p r e c a t i o n " / ) ;
62- const deprecationLink = matches && matches . pop ( ) ;
63- log . warn (
64- `[@octokit/request] "${ requestOptions . method } ${
65- requestOptions . url
66- } " is deprecated. It is scheduled to be removed on ${ headers . sunset } ${
67- deprecationLink ? `. See ${ deprecationLink } ` : ""
68- } `,
69- ) ;
70- }
71-
72- if ( status === 204 || status === 205 ) {
73- return ;
74- }
75-
76- // GitHub API returns 200 for HEAD requests
77- if ( requestOptions . method === "HEAD" ) {
78- if ( status < 400 ) {
79- return ;
80- }
81-
82- throw new RequestError ( response . statusText , status , {
83- response : {
84- url,
85- status,
86- headers,
87- data : undefined ,
88- } ,
89- request : requestOptions ,
90- } ) ;
91- }
92-
93- if ( status === 304 ) {
94- throw new RequestError ( "Not modified" , status , {
95- response : {
96- url,
97- status,
98- headers,
99- data : await getResponseData ( response ) ,
100- } ,
101- request : requestOptions ,
102- } ) ;
103- }
104-
105- if ( status >= 400 ) {
106- const data = await getResponseData ( response ) ;
107-
108- const error = new RequestError ( toErrorMessage ( data ) , status , {
109- response : {
110- url,
111- status,
112- headers,
113- data,
114- } ,
115- request : requestOptions ,
116- } ) ;
17+ const log = requestOptions . request ?. log || console ;
18+ const parseSuccessResponseBody =
19+ requestOptions . request ?. parseSuccessResponseBody !== false ;
11720
21+ const body =
22+ isPlainObject ( requestOptions . body ) || Array . isArray ( requestOptions . body )
23+ ? JSON . stringify ( requestOptions . body )
24+ : requestOptions . body ;
25+
26+ // Header values must be `string`
27+ const requestHeaders = Object . fromEntries (
28+ Object . entries ( requestOptions . headers ) . map ( ( [ name , value ] ) => [
29+ name ,
30+ String ( value ) ,
31+ ] ) ,
32+ ) ;
33+
34+ let fetchResponse : Response ;
35+
36+ try {
37+ fetchResponse = await fetch ( requestOptions . url , {
38+ method : requestOptions . method ,
39+ body,
40+ redirect : requestOptions . request ?. redirect ,
41+ headers : requestHeaders ,
42+ signal : requestOptions . request ?. signal ,
43+ // duplex must be set if request.body is ReadableStream or Async Iterables.
44+ // See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
45+ ...( requestOptions . body && { duplex : "half" } ) ,
46+ } ) ;
47+ // wrap fetch errors as RequestError if it is not a AbortError
48+ } catch ( error ) {
49+ let message = "Unknown Error" ;
50+ if ( error instanceof Error ) {
51+ if ( error . name === "AbortError" ) {
52+ ( error as RequestError ) . status = 500 ;
11853 throw error ;
11954 }
12055
121- return parseSuccessResponseBody
122- ? await getResponseData ( response )
123- : response . body ;
124- } )
125- . then ( ( data ) => {
126- return {
127- status,
128- url,
129- headers,
130- data,
131- } ;
132- } )
133- . catch ( ( error ) => {
134- if ( error instanceof RequestError ) throw error ;
135- else if ( error . name === "AbortError" ) throw error ;
136-
137- let message = error . message ;
56+ message = error . message ;
13857
13958 // undici throws a TypeError for network errors
14059 // and puts the error message in `error.cause`
@@ -146,14 +65,87 @@ export default function fetchWrapper(
14665 message = error . cause ;
14766 }
14867 }
68+ }
69+
70+ const requestError = new RequestError ( message , 500 , {
71+ request : requestOptions ,
72+ } ) ;
73+ requestError . cause = error ;
74+
75+ throw requestError ;
76+ }
77+
78+ const status = fetchResponse . status ;
79+ const url = fetchResponse . url ;
80+ const responseHeaders : { [ header : string ] : string } = { } ;
81+
82+ for ( const [ key , value ] of fetchResponse . headers ) {
83+ responseHeaders [ key ] = value ;
84+ }
85+
86+ const octokitResponse : OctokitResponse < any > = {
87+ url,
88+ status,
89+ headers : responseHeaders ,
90+ data : "" ,
91+ } ;
92+
93+ if ( "deprecation" in responseHeaders ) {
94+ const matches =
95+ responseHeaders . link &&
96+ responseHeaders . link . match ( / < ( [ ^ > ] + ) > ; r e l = " d e p r e c a t i o n " / ) ;
97+ const deprecationLink = matches && matches . pop ( ) ;
98+ log . warn (
99+ `[@octokit/request] "${ requestOptions . method } ${
100+ requestOptions . url
101+ } " is deprecated. It is scheduled to be removed on ${ responseHeaders . sunset } ${
102+ deprecationLink ? `. See ${ deprecationLink } ` : ""
103+ } `,
104+ ) ;
105+ }
106+
107+ if ( status === 204 || status === 205 ) {
108+ return octokitResponse ;
109+ }
110+
111+ // GitHub API returns 200 for HEAD requests
112+ if ( requestOptions . method === "HEAD" ) {
113+ if ( status < 400 ) {
114+ return octokitResponse ;
115+ }
116+
117+ throw new RequestError ( fetchResponse . statusText , status , {
118+ response : octokitResponse ,
119+ request : requestOptions ,
120+ } ) ;
121+ }
122+
123+ if ( status === 304 ) {
124+ octokitResponse . data = await getResponseData ( fetchResponse ) ;
125+
126+ throw new RequestError ( "Not modified" , status , {
127+ response : octokitResponse ,
128+ request : requestOptions ,
129+ } ) ;
130+ }
131+
132+ if ( status >= 400 ) {
133+ octokitResponse . data = await getResponseData ( fetchResponse ) ;
149134
150- throw new RequestError ( message , 500 , {
151- request : requestOptions ,
152- } ) ;
135+ throw new RequestError ( toErrorMessage ( octokitResponse . data ) , status , {
136+ response : octokitResponse ,
137+ request : requestOptions ,
153138 } ) ;
139+ }
140+
141+ octokitResponse . data = parseSuccessResponseBody
142+ ? await getResponseData ( fetchResponse )
143+ : fetchResponse . body ;
144+
145+ return octokitResponse ;
154146}
155147
156- async function getResponseData ( response : Response ) {
148+ async function getResponseData ( response : Response ) : Promise < any > {
157149 const contentType = response . headers . get ( "content-type" ) ;
158150 if ( / a p p l i c a t i o n \/ j s o n / . test ( contentType ! ) ) {
159151 return (
0 commit comments