@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +241 -0
  4. package/docs/caching.md +76 -0
  5. package/docs/configuration.md +97 -0
  6. package/docs/deployment.md +74 -0
  7. package/docs/getting-started.md +82 -0
  8. package/docs/middleware.md +58 -0
  9. package/docs/releasing.md +299 -0
  10. package/docs/routing.md +103 -0
  11. package/docs/security.md +57 -0
  12. package/package.json +68 -0
  13. package/packages/bundler/src/client-islands.ts +1457 -0
  14. package/packages/bundler/src/incremental-context.ts +206 -0
  15. package/packages/bundler/src/index.ts +1991 -0
  16. package/packages/bundler/src/module-graph.ts +317 -0
  17. package/packages/bundler/src/partial-hydration.ts +414 -0
  18. package/packages/bundler/src/production.ts +974 -0
  19. package/packages/bundler/src/server-production-middleware.ts +447 -0
  20. package/packages/bundler/src/server-production.ts +1193 -0
  21. package/packages/bundler/src/special-files.ts +131 -0
  22. package/packages/cache/src/index.ts +761 -0
  23. package/packages/cli/bin/bcp.mjs +93 -0
  24. package/packages/cli/src/args.ts +305 -0
  25. package/packages/cli/src/bootstrap.ts +514 -0
  26. package/packages/cli/src/index.ts +504 -0
  27. package/packages/cli/src/version.ts +45 -0
  28. package/packages/client/src/cache.ts +11 -0
  29. package/packages/client/src/config.ts +18 -0
  30. package/packages/client/src/error-boundary.tsx +149 -0
  31. package/packages/client/src/hydration.ts +3 -0
  32. package/packages/client/src/index.tsx +57 -0
  33. package/packages/client/src/islands.tsx +315 -0
  34. package/packages/client/src/metadata.ts +281 -0
  35. package/packages/client/src/navigation-loading.ts +52 -0
  36. package/packages/client/src/navigation-state.ts +80 -0
  37. package/packages/client/src/not-found.ts +34 -0
  38. package/packages/client/src/persistent-layout-runtime.ts +273 -0
  39. package/packages/client/src/router-v2.tsx +969 -0
  40. package/packages/client/src/router.tsx +1 -0
  41. package/packages/config/src/index.ts +1038 -0
  42. package/packages/env/src/index.ts +593 -0
  43. package/packages/router/src/advanced-router.ts +1032 -0
  44. package/packages/router/src/index.ts +1 -0
  45. package/packages/server/src/compression.ts +249 -0
  46. package/packages/server/src/dev-document-metadata.ts +154 -0
  47. package/packages/server/src/dev-hmr.ts +225 -0
  48. package/packages/server/src/index.ts +2265 -0
  49. package/packages/server/src/metadata.ts +478 -0
  50. package/packages/server/src/middleware-dev-server.ts +260 -0
  51. package/packages/server/src/middleware-loader.ts +140 -0
  52. package/packages/server/src/middleware-proxy.ts +516 -0
  53. package/packages/server/src/middleware.ts +704 -0
  54. package/packages/server/src/navigation-payload.ts +471 -0
  55. package/packages/server/src/production-server.ts +1746 -0
  56. package/packages/server/src/response-cache-proxy.ts +828 -0
  57. package/packages/server/src/security-proxy.ts +406 -0
  58. package/packages/server/src/security.ts +451 -0
  59. package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
  60. package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
  61. package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
  62. package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
  63. package/packages/server/src/standalone-production-runtime.ts +1951 -0
  64. package/packages/server/src/standalone-production-server.ts +6 -0
  65. package/packages/server/src/static-assets.ts +262 -0
  66. package/packages/server/src/static-dev-server.ts +854 -0
@@ -0,0 +1,406 @@
1
+ import * as http from "node:http";
2
+
3
+ import {
4
+ applySecurityHeaders,
5
+ getConfiguredBodyLimit,
6
+ } from "./security.js";
7
+
8
+ export interface SecurityProxyOptions {
9
+ port: number;
10
+ hostname: string;
11
+ upstreamPort: number;
12
+ upstreamHostname?: string;
13
+ }
14
+
15
+ export function createSecurityProxy(
16
+ options: SecurityProxyOptions
17
+ ) {
18
+ const upstreamHostname =
19
+ options.upstreamHostname ??
20
+ "127.0.0.1";
21
+
22
+ let server:
23
+ http.Server | null =
24
+ null;
25
+
26
+ async function start(): Promise<void> {
27
+ if (server) {
28
+ return;
29
+ }
30
+
31
+ server =
32
+ http.createServer(
33
+ (
34
+ req,
35
+ res
36
+ ) => {
37
+ applySecurityHeaders(
38
+ res
39
+ );
40
+
41
+ const bodyLimit =
42
+ getConfiguredBodyLimit();
43
+
44
+ if (
45
+ declaredBodyTooLarge(
46
+ req,
47
+ bodyLimit
48
+ )
49
+ ) {
50
+ req.resume();
51
+ sendPayloadTooLarge(
52
+ res,
53
+ bodyLimit
54
+ );
55
+ return;
56
+ }
57
+
58
+ proxyRequest(
59
+ req,
60
+ res,
61
+ upstreamHostname,
62
+ options.upstreamPort,
63
+ bodyLimit
64
+ );
65
+ }
66
+ );
67
+
68
+ server.keepAliveTimeout =
69
+ 65_000;
70
+ server.headersTimeout =
71
+ 66_000;
72
+
73
+ await listen(
74
+ server,
75
+ options.port,
76
+ options.hostname
77
+ );
78
+ }
79
+
80
+ async function stop(): Promise<void> {
81
+ if (!server) {
82
+ return;
83
+ }
84
+
85
+ const current =
86
+ server;
87
+ server =
88
+ null;
89
+
90
+ await closeServer(
91
+ current
92
+ );
93
+ }
94
+
95
+ return {
96
+ start,
97
+ stop,
98
+ };
99
+ }
100
+
101
+ function proxyRequest(
102
+ req: http.IncomingMessage,
103
+ res: http.ServerResponse,
104
+ upstreamHostname: string,
105
+ upstreamPort: number,
106
+ bodyLimit: number
107
+ ): void {
108
+ const upstream =
109
+ http.request({
110
+ hostname:
111
+ upstreamHostname,
112
+ port:
113
+ upstreamPort,
114
+ method:
115
+ req.method ??
116
+ "GET",
117
+ path:
118
+ req.url ??
119
+ "/",
120
+ headers:
121
+ req.headers,
122
+ });
123
+
124
+ let received = 0;
125
+ let rejected = false;
126
+
127
+ upstream.on(
128
+ "response",
129
+ (upstreamResponse) => {
130
+ if (rejected) {
131
+ upstreamResponse.resume();
132
+ return;
133
+ }
134
+
135
+ for (
136
+ const [
137
+ name,
138
+ value,
139
+ ]
140
+ of Object.entries(
141
+ upstreamResponse.headers
142
+ )
143
+ ) {
144
+ if (
145
+ value !== undefined
146
+ ) {
147
+ res.setHeader(
148
+ name,
149
+ value
150
+ );
151
+ }
152
+ }
153
+
154
+ res.statusCode =
155
+ upstreamResponse.statusCode ??
156
+ 502;
157
+
158
+ if (
159
+ upstreamResponse.statusMessage
160
+ ) {
161
+ res.statusMessage =
162
+ upstreamResponse.statusMessage;
163
+ }
164
+
165
+ upstreamResponse.pipe(
166
+ res
167
+ );
168
+ }
169
+ );
170
+
171
+ upstream.on(
172
+ "error",
173
+ (error) => {
174
+ if (rejected) {
175
+ return;
176
+ }
177
+
178
+ console.error(
179
+ "[BCP Security] upstream request failed:",
180
+ error
181
+ );
182
+
183
+ if (
184
+ res.headersSent
185
+ ) {
186
+ res.destroy(
187
+ error
188
+ );
189
+ return;
190
+ }
191
+
192
+ res.writeHead(
193
+ 502,
194
+ {
195
+ "Content-Type":
196
+ "text/plain; charset=utf-8",
197
+ "Cache-Control":
198
+ "no-store",
199
+ }
200
+ );
201
+ res.end(
202
+ "Bad Gateway"
203
+ );
204
+ }
205
+ );
206
+
207
+ req.on(
208
+ "data",
209
+ (chunk) => {
210
+ if (rejected) {
211
+ return;
212
+ }
213
+
214
+ const buffer =
215
+ Buffer.isBuffer(
216
+ chunk
217
+ )
218
+ ? chunk
219
+ : Buffer.from(
220
+ chunk
221
+ );
222
+
223
+ received +=
224
+ buffer.length;
225
+
226
+ if (
227
+ received > bodyLimit
228
+ ) {
229
+ rejected =
230
+ true;
231
+ upstream.destroy();
232
+
233
+ if (
234
+ !res.headersSent
235
+ ) {
236
+ sendPayloadTooLarge(
237
+ res,
238
+ bodyLimit
239
+ );
240
+ } else {
241
+ res.destroy();
242
+ }
243
+
244
+ return;
245
+ }
246
+
247
+ upstream.write(
248
+ buffer
249
+ );
250
+ }
251
+ );
252
+
253
+ req.on(
254
+ "end",
255
+ () => {
256
+ if (!rejected) {
257
+ upstream.end();
258
+ }
259
+ }
260
+ );
261
+
262
+ req.on(
263
+ "error",
264
+ (error) => {
265
+ upstream.destroy(
266
+ error
267
+ );
268
+ }
269
+ );
270
+ }
271
+
272
+ function declaredBodyTooLarge(
273
+ req: http.IncomingMessage,
274
+ limit: number
275
+ ): boolean {
276
+ const raw =
277
+ req.headers[
278
+ "content-length"
279
+ ];
280
+ const value =
281
+ Array.isArray(
282
+ raw
283
+ )
284
+ ? raw[0]
285
+ : raw;
286
+
287
+ if (!value) {
288
+ return false;
289
+ }
290
+
291
+ const parsed =
292
+ Number(
293
+ value
294
+ );
295
+
296
+ return (
297
+ Number.isInteger(
298
+ parsed
299
+ ) &&
300
+ parsed > limit
301
+ );
302
+ }
303
+
304
+ function sendPayloadTooLarge(
305
+ res: http.ServerResponse,
306
+ limit: number
307
+ ): void {
308
+ const body =
309
+ Buffer.from(
310
+ JSON.stringify({
311
+ error:
312
+ "Payload Too Large",
313
+ limit,
314
+ })
315
+ );
316
+
317
+ res.writeHead(
318
+ 413,
319
+ {
320
+ "Content-Type":
321
+ "application/json; charset=utf-8",
322
+ "Content-Length":
323
+ body.length,
324
+ "Cache-Control":
325
+ "no-store",
326
+ }
327
+ );
328
+ res.end(
329
+ body
330
+ );
331
+ }
332
+
333
+ function listen(
334
+ server: http.Server,
335
+ port: number,
336
+ hostname: string
337
+ ): Promise<void> {
338
+ return new Promise(
339
+ (
340
+ resolve,
341
+ reject
342
+ ) => {
343
+ const onError =
344
+ (error: Error) => {
345
+ server.off(
346
+ "listening",
347
+ onListening
348
+ );
349
+ reject(
350
+ error
351
+ );
352
+ };
353
+
354
+ const onListening =
355
+ () => {
356
+ server.off(
357
+ "error",
358
+ onError
359
+ );
360
+ resolve();
361
+ };
362
+
363
+ server.once(
364
+ "error",
365
+ onError
366
+ );
367
+ server.once(
368
+ "listening",
369
+ onListening
370
+ );
371
+ server.listen(
372
+ port,
373
+ hostname
374
+ );
375
+ }
376
+ );
377
+ }
378
+
379
+ function closeServer(
380
+ server: http.Server
381
+ ): Promise<void> {
382
+ if (
383
+ !server.listening
384
+ ) {
385
+ return Promise.resolve();
386
+ }
387
+
388
+ return new Promise(
389
+ (
390
+ resolve,
391
+ reject
392
+ ) => {
393
+ server.close(
394
+ (error) => {
395
+ if (error) {
396
+ reject(
397
+ error
398
+ );
399
+ return;
400
+ }
401
+ resolve();
402
+ }
403
+ );
404
+ }
405
+ );
406
+ }