1
1
import { isPlainObject } from "./is-plain-object.js" ;
2
2
import { RequestError } from "@octokit/request-error" ;
3
- import type { EndpointInterface } from "@octokit/types" ;
3
+ import type { EndpointInterface , OctokitResponse } from "@octokit/types" ;
4
4
5
- export default function fetchWrapper (
5
+ export default async function fetchWrapper (
6
6
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 > > {
26
8
const fetch : typeof globalThis . fetch =
27
9
requestOptions . request ?. fetch || globalThis . fetch ;
28
10
@@ -32,109 +14,46 @@ export default function fetchWrapper(
32
14
) ;
33
15
}
34
16
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 ;
117
20
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 ;
118
53
throw error ;
119
54
}
120
55
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 ;
138
57
139
58
// undici throws a TypeError for network errors
140
59
// and puts the error message in `error.cause`
@@ -146,14 +65,87 @@ export default function fetchWrapper(
146
65
message = error . cause ;
147
66
}
148
67
}
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 ) ;
149
134
150
- throw new RequestError ( message , 500 , {
151
- request : requestOptions ,
152
- } ) ;
135
+ throw new RequestError ( toErrorMessage ( octokitResponse . data ) , status , {
136
+ response : octokitResponse ,
137
+ request : requestOptions ,
153
138
} ) ;
139
+ }
140
+
141
+ octokitResponse . data = parseSuccessResponseBody
142
+ ? await getResponseData ( fetchResponse )
143
+ : fetchResponse . body ;
144
+
145
+ return octokitResponse ;
154
146
}
155
147
156
- async function getResponseData ( response : Response ) {
148
+ async function getResponseData ( response : Response ) : Promise < any > {
157
149
const contentType = response . headers . get ( "content-type" ) ;
158
150
if ( / a p p l i c a t i o n \/ j s o n / . test ( contentType ! ) ) {
159
151
return (
0 commit comments