@chidchanun/bcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/docs/caching.md +76 -0
- package/docs/configuration.md +97 -0
- package/docs/deployment.md +74 -0
- package/docs/getting-started.md +82 -0
- package/docs/middleware.md +58 -0
- package/docs/releasing.md +299 -0
- package/docs/routing.md +103 -0
- package/docs/security.md +57 -0
- package/package.json +68 -0
- package/packages/bundler/src/client-islands.ts +1457 -0
- package/packages/bundler/src/incremental-context.ts +206 -0
- package/packages/bundler/src/index.ts +1991 -0
- package/packages/bundler/src/module-graph.ts +317 -0
- package/packages/bundler/src/partial-hydration.ts +414 -0
- package/packages/bundler/src/production.ts +974 -0
- package/packages/bundler/src/server-production-middleware.ts +447 -0
- package/packages/bundler/src/server-production.ts +1193 -0
- package/packages/bundler/src/special-files.ts +131 -0
- package/packages/cache/src/index.ts +761 -0
- package/packages/cli/bin/bcp.mjs +93 -0
- package/packages/cli/src/args.ts +305 -0
- package/packages/cli/src/bootstrap.ts +514 -0
- package/packages/cli/src/index.ts +504 -0
- package/packages/cli/src/version.ts +45 -0
- package/packages/client/src/cache.ts +11 -0
- package/packages/client/src/config.ts +18 -0
- package/packages/client/src/error-boundary.tsx +149 -0
- package/packages/client/src/hydration.ts +3 -0
- package/packages/client/src/index.tsx +57 -0
- package/packages/client/src/islands.tsx +315 -0
- package/packages/client/src/metadata.ts +281 -0
- package/packages/client/src/navigation-loading.ts +52 -0
- package/packages/client/src/navigation-state.ts +80 -0
- package/packages/client/src/not-found.ts +34 -0
- package/packages/client/src/persistent-layout-runtime.ts +273 -0
- package/packages/client/src/router-v2.tsx +969 -0
- package/packages/client/src/router.tsx +1 -0
- package/packages/config/src/index.ts +1038 -0
- package/packages/env/src/index.ts +593 -0
- package/packages/router/src/advanced-router.ts +1032 -0
- package/packages/router/src/index.ts +1 -0
- package/packages/server/src/compression.ts +249 -0
- package/packages/server/src/dev-document-metadata.ts +154 -0
- package/packages/server/src/dev-hmr.ts +225 -0
- package/packages/server/src/index.ts +2265 -0
- package/packages/server/src/metadata.ts +478 -0
- package/packages/server/src/middleware-dev-server.ts +260 -0
- package/packages/server/src/middleware-loader.ts +140 -0
- package/packages/server/src/middleware-proxy.ts +516 -0
- package/packages/server/src/middleware.ts +704 -0
- package/packages/server/src/navigation-payload.ts +471 -0
- package/packages/server/src/production-server.ts +1746 -0
- package/packages/server/src/response-cache-proxy.ts +828 -0
- package/packages/server/src/security-proxy.ts +406 -0
- package/packages/server/src/security.ts +451 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
- package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
- package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
- package/packages/server/src/standalone-production-runtime.ts +1951 -0
- package/packages/server/src/standalone-production-server.ts +6 -0
- package/packages/server/src/static-assets.ts +262 -0
- package/packages/server/src/static-dev-server.ts +854 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
pathToFileURL,
|
|
5
|
+
} from "node:url";
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
MiddlewareModule,
|
|
9
|
+
} from "./middleware.js";
|
|
10
|
+
|
|
11
|
+
const MIDDLEWARE_EXTENSIONS = [
|
|
12
|
+
".ts",
|
|
13
|
+
".tsx",
|
|
14
|
+
".js",
|
|
15
|
+
".jsx",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export function findProjectMiddlewareFile(
|
|
19
|
+
rootDirectory: string
|
|
20
|
+
): string | null {
|
|
21
|
+
const matches =
|
|
22
|
+
MIDDLEWARE_EXTENSIONS
|
|
23
|
+
.map(
|
|
24
|
+
(extension) =>
|
|
25
|
+
path.join(
|
|
26
|
+
rootDirectory,
|
|
27
|
+
`middleware${extension}`
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
.filter(
|
|
31
|
+
(filePath) =>
|
|
32
|
+
fs.existsSync(
|
|
33
|
+
filePath
|
|
34
|
+
) &&
|
|
35
|
+
fs.statSync(
|
|
36
|
+
filePath
|
|
37
|
+
).isFile()
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
matches.length > 1
|
|
42
|
+
) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`BCP Framework: multiple middleware files found: ${matches.map((value) => path.basename(value)).join(", ")}. Keep only one middleware.ts/.tsx/.js/.jsx file.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return matches[0] ??
|
|
49
|
+
null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function loadProjectMiddleware(
|
|
53
|
+
rootDirectory: string
|
|
54
|
+
): Promise<MiddlewareModule | null> {
|
|
55
|
+
const filePath =
|
|
56
|
+
findProjectMiddlewareFile(
|
|
57
|
+
rootDirectory
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!filePath) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stat =
|
|
65
|
+
fs.statSync(
|
|
66
|
+
filePath
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const moduleUrl =
|
|
70
|
+
pathToFileURL(
|
|
71
|
+
filePath
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
moduleUrl.searchParams.set(
|
|
75
|
+
"bcp-middleware",
|
|
76
|
+
`${stat.mtimeMs}-${stat.size}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const module =
|
|
80
|
+
await import(
|
|
81
|
+
moduleUrl.href
|
|
82
|
+
) as MiddlewareModule;
|
|
83
|
+
|
|
84
|
+
assertMiddlewareModule(
|
|
85
|
+
module,
|
|
86
|
+
filePath
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return module;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function assertMiddlewareModule(
|
|
93
|
+
module: MiddlewareModule,
|
|
94
|
+
filePath = "middleware"
|
|
95
|
+
): void {
|
|
96
|
+
const handler =
|
|
97
|
+
module.middleware ??
|
|
98
|
+
module.default;
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
typeof handler !==
|
|
102
|
+
"function"
|
|
103
|
+
) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`BCP Framework: ${filePath} must export a middleware(request) function or a default middleware function.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const matcher =
|
|
110
|
+
module.config?.matcher;
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
matcher === undefined
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const values =
|
|
119
|
+
Array.isArray(
|
|
120
|
+
matcher
|
|
121
|
+
)
|
|
122
|
+
? matcher
|
|
123
|
+
: [
|
|
124
|
+
matcher,
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
for (
|
|
128
|
+
const value
|
|
129
|
+
of values
|
|
130
|
+
) {
|
|
131
|
+
if (
|
|
132
|
+
typeof value !== "string" &&
|
|
133
|
+
!(value instanceof RegExp)
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`BCP Framework: middleware matcher in ${filePath} must be a string, RegExp, or an array of them.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
applyMiddlewareRequestHeaders,
|
|
5
|
+
applyMiddlewareResponseHeaders,
|
|
6
|
+
executeMiddleware,
|
|
7
|
+
sendMiddlewareRedirect,
|
|
8
|
+
sendMiddlewareResponse,
|
|
9
|
+
type MiddlewareModule,
|
|
10
|
+
} from "./middleware.js";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
applySecurityHeaders,
|
|
14
|
+
} from "./security.js";
|
|
15
|
+
|
|
16
|
+
export const NAVIGATION_REDIRECT_HEADER =
|
|
17
|
+
"X-BCP-Middleware-Redirect";
|
|
18
|
+
|
|
19
|
+
export interface MiddlewareProxyOptions {
|
|
20
|
+
port: number;
|
|
21
|
+
hostname: string;
|
|
22
|
+
upstreamPort: number;
|
|
23
|
+
upstreamHostname?: string;
|
|
24
|
+
getMiddleware:
|
|
25
|
+
() =>
|
|
26
|
+
MiddlewareModule |
|
|
27
|
+
null |
|
|
28
|
+
Promise<
|
|
29
|
+
MiddlewareModule |
|
|
30
|
+
null
|
|
31
|
+
>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createMiddlewareProxy(
|
|
35
|
+
options: MiddlewareProxyOptions
|
|
36
|
+
) {
|
|
37
|
+
const upstreamHostname =
|
|
38
|
+
options.upstreamHostname ??
|
|
39
|
+
"127.0.0.1";
|
|
40
|
+
|
|
41
|
+
let server:
|
|
42
|
+
http.Server | null =
|
|
43
|
+
null;
|
|
44
|
+
|
|
45
|
+
async function start(): Promise<void> {
|
|
46
|
+
if (server) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
server =
|
|
51
|
+
http.createServer(
|
|
52
|
+
async (
|
|
53
|
+
req,
|
|
54
|
+
res
|
|
55
|
+
) => {
|
|
56
|
+
applySecurityHeaders(
|
|
57
|
+
res
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const host =
|
|
62
|
+
req.headers.host ??
|
|
63
|
+
`${options.hostname}:${options.port}`;
|
|
64
|
+
|
|
65
|
+
const incomingUrl =
|
|
66
|
+
new URL(
|
|
67
|
+
req.url ?? "/",
|
|
68
|
+
`http://${host}`
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const navigationTarget =
|
|
72
|
+
resolveNavigationTarget(
|
|
73
|
+
incomingUrl
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const targetUrl =
|
|
77
|
+
navigationTarget ??
|
|
78
|
+
incomingUrl;
|
|
79
|
+
|
|
80
|
+
const module =
|
|
81
|
+
await options
|
|
82
|
+
.getMiddleware();
|
|
83
|
+
|
|
84
|
+
const result =
|
|
85
|
+
await executeMiddleware(
|
|
86
|
+
module,
|
|
87
|
+
{
|
|
88
|
+
url:
|
|
89
|
+
targetUrl,
|
|
90
|
+
method:
|
|
91
|
+
req.method ??
|
|
92
|
+
"GET",
|
|
93
|
+
headers:
|
|
94
|
+
incomingHeadersToHeaders(
|
|
95
|
+
req.headers
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
result.type ===
|
|
102
|
+
"redirect"
|
|
103
|
+
) {
|
|
104
|
+
if (
|
|
105
|
+
navigationTarget
|
|
106
|
+
) {
|
|
107
|
+
applyMiddlewareResponseHeaders(
|
|
108
|
+
res,
|
|
109
|
+
result.headers
|
|
110
|
+
);
|
|
111
|
+
res.statusCode =
|
|
112
|
+
204;
|
|
113
|
+
res.setHeader(
|
|
114
|
+
NAVIGATION_REDIRECT_HEADER,
|
|
115
|
+
result.url.href
|
|
116
|
+
);
|
|
117
|
+
res.setHeader(
|
|
118
|
+
"Cache-Control",
|
|
119
|
+
"no-store"
|
|
120
|
+
);
|
|
121
|
+
res.end();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
sendMiddlewareRedirect(
|
|
126
|
+
req,
|
|
127
|
+
res,
|
|
128
|
+
result
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
result.type ===
|
|
135
|
+
"response"
|
|
136
|
+
) {
|
|
137
|
+
await sendMiddlewareResponse(
|
|
138
|
+
req,
|
|
139
|
+
res,
|
|
140
|
+
result.response
|
|
141
|
+
);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
applyMiddlewareRequestHeaders(
|
|
146
|
+
req,
|
|
147
|
+
result.requestHeaders
|
|
148
|
+
);
|
|
149
|
+
applyMiddlewareResponseHeaders(
|
|
150
|
+
res,
|
|
151
|
+
result.responseHeaders
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
result.type ===
|
|
156
|
+
"rewrite"
|
|
157
|
+
) {
|
|
158
|
+
if (
|
|
159
|
+
navigationTarget
|
|
160
|
+
) {
|
|
161
|
+
incomingUrl
|
|
162
|
+
.searchParams
|
|
163
|
+
.set(
|
|
164
|
+
"url",
|
|
165
|
+
`${result.url.pathname}${result.url.search}${result.url.hash}`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
req.url =
|
|
169
|
+
`${incomingUrl.pathname}${incomingUrl.search}`;
|
|
170
|
+
} else {
|
|
171
|
+
req.url =
|
|
172
|
+
`${result.url.pathname}${result.url.search}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
proxyRequest(
|
|
177
|
+
req,
|
|
178
|
+
res,
|
|
179
|
+
upstreamHostname,
|
|
180
|
+
options.upstreamPort,
|
|
181
|
+
host
|
|
182
|
+
);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(
|
|
185
|
+
"[BCP Middleware] request failed:",
|
|
186
|
+
error
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
res.headersSent
|
|
191
|
+
) {
|
|
192
|
+
res.destroy();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
res.writeHead(
|
|
197
|
+
500,
|
|
198
|
+
{
|
|
199
|
+
"Content-Type":
|
|
200
|
+
"text/plain; charset=utf-8",
|
|
201
|
+
"Cache-Control":
|
|
202
|
+
"no-store",
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
res.end(
|
|
206
|
+
"Internal Server Error"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
server.keepAliveTimeout =
|
|
213
|
+
65_000;
|
|
214
|
+
server.headersTimeout =
|
|
215
|
+
66_000;
|
|
216
|
+
|
|
217
|
+
await listen(
|
|
218
|
+
server,
|
|
219
|
+
options.port,
|
|
220
|
+
options.hostname
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function stop(): Promise<void> {
|
|
225
|
+
if (!server) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const current =
|
|
230
|
+
server;
|
|
231
|
+
server =
|
|
232
|
+
null;
|
|
233
|
+
|
|
234
|
+
await closeServer(
|
|
235
|
+
current
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
start,
|
|
241
|
+
stop,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function resolveNavigationTarget(
|
|
246
|
+
requestUrl: URL
|
|
247
|
+
): URL | null {
|
|
248
|
+
if (
|
|
249
|
+
requestUrl.pathname !==
|
|
250
|
+
"/_bcp/navigation"
|
|
251
|
+
) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const rawTarget =
|
|
256
|
+
requestUrl.searchParams
|
|
257
|
+
.get(
|
|
258
|
+
"url"
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (!rawTarget) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const target =
|
|
267
|
+
new URL(
|
|
268
|
+
rawTarget,
|
|
269
|
+
requestUrl.origin
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
target.origin !==
|
|
274
|
+
requestUrl.origin
|
|
275
|
+
) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return target;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function proxyRequest(
|
|
286
|
+
req: http.IncomingMessage,
|
|
287
|
+
res: http.ServerResponse,
|
|
288
|
+
upstreamHostname: string,
|
|
289
|
+
upstreamPort: number,
|
|
290
|
+
originalHost: string
|
|
291
|
+
): void {
|
|
292
|
+
const upstream =
|
|
293
|
+
http.request(
|
|
294
|
+
{
|
|
295
|
+
hostname:
|
|
296
|
+
upstreamHostname,
|
|
297
|
+
port:
|
|
298
|
+
upstreamPort,
|
|
299
|
+
method:
|
|
300
|
+
req.method ??
|
|
301
|
+
"GET",
|
|
302
|
+
path:
|
|
303
|
+
req.url ??
|
|
304
|
+
"/",
|
|
305
|
+
headers: {
|
|
306
|
+
...req.headers,
|
|
307
|
+
host:
|
|
308
|
+
originalHost,
|
|
309
|
+
"x-forwarded-host":
|
|
310
|
+
originalHost,
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
upstream.on(
|
|
316
|
+
"response",
|
|
317
|
+
(upstreamResponse) => {
|
|
318
|
+
const headers = {
|
|
319
|
+
...upstreamResponse.headers,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
for (
|
|
323
|
+
const [
|
|
324
|
+
name,
|
|
325
|
+
value,
|
|
326
|
+
]
|
|
327
|
+
of Object.entries(
|
|
328
|
+
headers
|
|
329
|
+
)
|
|
330
|
+
) {
|
|
331
|
+
if (
|
|
332
|
+
value !== undefined
|
|
333
|
+
) {
|
|
334
|
+
res.setHeader(
|
|
335
|
+
name,
|
|
336
|
+
value
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
res.statusCode =
|
|
342
|
+
upstreamResponse.statusCode ??
|
|
343
|
+
502;
|
|
344
|
+
|
|
345
|
+
if (
|
|
346
|
+
upstreamResponse.statusMessage
|
|
347
|
+
) {
|
|
348
|
+
res.statusMessage =
|
|
349
|
+
upstreamResponse.statusMessage;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
upstreamResponse.pipe(
|
|
353
|
+
res
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
upstream.on(
|
|
359
|
+
"error",
|
|
360
|
+
(error) => {
|
|
361
|
+
console.error(
|
|
362
|
+
"[BCP Middleware] upstream request failed:",
|
|
363
|
+
error
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (
|
|
367
|
+
res.headersSent
|
|
368
|
+
) {
|
|
369
|
+
res.destroy(
|
|
370
|
+
error
|
|
371
|
+
);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
res.writeHead(
|
|
376
|
+
502,
|
|
377
|
+
{
|
|
378
|
+
"Content-Type":
|
|
379
|
+
"text/plain; charset=utf-8",
|
|
380
|
+
"Cache-Control":
|
|
381
|
+
"no-store",
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
res.end(
|
|
385
|
+
"Bad Gateway"
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
req.pipe(
|
|
391
|
+
upstream
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function incomingHeadersToHeaders(
|
|
396
|
+
input: http.IncomingHttpHeaders
|
|
397
|
+
): Headers {
|
|
398
|
+
const headers =
|
|
399
|
+
new Headers();
|
|
400
|
+
|
|
401
|
+
for (
|
|
402
|
+
const [
|
|
403
|
+
key,
|
|
404
|
+
value,
|
|
405
|
+
]
|
|
406
|
+
of Object.entries(
|
|
407
|
+
input
|
|
408
|
+
)
|
|
409
|
+
) {
|
|
410
|
+
if (
|
|
411
|
+
value === undefined
|
|
412
|
+
) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
Array.isArray(
|
|
418
|
+
value
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
for (
|
|
422
|
+
const item
|
|
423
|
+
of value
|
|
424
|
+
) {
|
|
425
|
+
headers.append(
|
|
426
|
+
key,
|
|
427
|
+
item
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
headers.set(
|
|
434
|
+
key,
|
|
435
|
+
value
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return headers;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function listen(
|
|
443
|
+
server: http.Server,
|
|
444
|
+
port: number,
|
|
445
|
+
hostname: string
|
|
446
|
+
): Promise<void> {
|
|
447
|
+
return new Promise(
|
|
448
|
+
(
|
|
449
|
+
resolve,
|
|
450
|
+
reject
|
|
451
|
+
) => {
|
|
452
|
+
const onError =
|
|
453
|
+
(error: Error) => {
|
|
454
|
+
server.off(
|
|
455
|
+
"listening",
|
|
456
|
+
onListening
|
|
457
|
+
);
|
|
458
|
+
reject(
|
|
459
|
+
error
|
|
460
|
+
);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const onListening =
|
|
464
|
+
() => {
|
|
465
|
+
server.off(
|
|
466
|
+
"error",
|
|
467
|
+
onError
|
|
468
|
+
);
|
|
469
|
+
resolve();
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
server.once(
|
|
473
|
+
"error",
|
|
474
|
+
onError
|
|
475
|
+
);
|
|
476
|
+
server.once(
|
|
477
|
+
"listening",
|
|
478
|
+
onListening
|
|
479
|
+
);
|
|
480
|
+
server.listen(
|
|
481
|
+
port,
|
|
482
|
+
hostname
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function closeServer(
|
|
489
|
+
server: http.Server
|
|
490
|
+
): Promise<void> {
|
|
491
|
+
if (
|
|
492
|
+
!server.listening
|
|
493
|
+
) {
|
|
494
|
+
return Promise.resolve();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return new Promise(
|
|
498
|
+
(
|
|
499
|
+
resolve,
|
|
500
|
+
reject
|
|
501
|
+
) => {
|
|
502
|
+
server.close(
|
|
503
|
+
(error) => {
|
|
504
|
+
if (error) {
|
|
505
|
+
reject(
|
|
506
|
+
error
|
|
507
|
+
);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
resolve();
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
}
|