@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,451 @@
1
+ import type * as http from "node:http";
2
+
3
+ export const DEFAULT_BODY_LIMIT =
4
+ 1024 * 1024;
5
+
6
+ export class PayloadTooLargeError
7
+ extends Error {
8
+ readonly limit: number;
9
+
10
+ constructor(
11
+ limit: number
12
+ ) {
13
+ super(
14
+ `Request body exceeded the configured limit of ${limit} bytes.`
15
+ );
16
+ this.name =
17
+ "PayloadTooLargeError";
18
+ this.limit =
19
+ limit;
20
+ }
21
+ }
22
+
23
+ export interface SecurityHeaderValues {
24
+ poweredByHeader: boolean;
25
+ contentSecurityPolicy: string | null;
26
+ frameOptions: "DENY" | "SAMEORIGIN" | null;
27
+ referrerPolicy: string;
28
+ permissionsPolicy: string | null;
29
+ }
30
+
31
+ export function getSecurityHeaderValues(
32
+ environment: NodeJS.ProcessEnv = process.env
33
+ ): SecurityHeaderValues {
34
+ return {
35
+ poweredByHeader:
36
+ readBoolean(
37
+ environment.BCP_POWERED_BY_HEADER,
38
+ false
39
+ ),
40
+ contentSecurityPolicy:
41
+ readOptionalHeader(
42
+ environment.BCP_SECURITY_CSP
43
+ ),
44
+ frameOptions:
45
+ readFrameOptions(
46
+ environment.BCP_SECURITY_FRAME_OPTIONS
47
+ ),
48
+ referrerPolicy:
49
+ readRequiredHeader(
50
+ environment.BCP_SECURITY_REFERRER_POLICY,
51
+ "strict-origin-when-cross-origin"
52
+ ),
53
+ permissionsPolicy:
54
+ readOptionalHeader(
55
+ environment.BCP_SECURITY_PERMISSIONS_POLICY,
56
+ "camera=(), microphone=(), geolocation=()"
57
+ ),
58
+ };
59
+ }
60
+
61
+ export function applySecurityHeaders(
62
+ res: http.ServerResponse,
63
+ environment: NodeJS.ProcessEnv = process.env
64
+ ): void {
65
+ const values =
66
+ getSecurityHeaderValues(
67
+ environment
68
+ );
69
+
70
+ setIfMissing(
71
+ res,
72
+ "X-Content-Type-Options",
73
+ "nosniff"
74
+ );
75
+ setIfMissing(
76
+ res,
77
+ "Referrer-Policy",
78
+ values.referrerPolicy
79
+ );
80
+
81
+ if (
82
+ values.frameOptions
83
+ ) {
84
+ setIfMissing(
85
+ res,
86
+ "X-Frame-Options",
87
+ values.frameOptions
88
+ );
89
+ }
90
+
91
+ if (
92
+ values.permissionsPolicy
93
+ ) {
94
+ setIfMissing(
95
+ res,
96
+ "Permissions-Policy",
97
+ values.permissionsPolicy
98
+ );
99
+ }
100
+
101
+ if (
102
+ values.contentSecurityPolicy
103
+ ) {
104
+ setIfMissing(
105
+ res,
106
+ "Content-Security-Policy",
107
+ values.contentSecurityPolicy
108
+ );
109
+ }
110
+
111
+ if (
112
+ values.poweredByHeader
113
+ ) {
114
+ setIfMissing(
115
+ res,
116
+ "X-Powered-By",
117
+ "BCP Framework"
118
+ );
119
+ } else if (
120
+ res.hasHeader(
121
+ "X-Powered-By"
122
+ )
123
+ ) {
124
+ res.removeHeader(
125
+ "X-Powered-By"
126
+ );
127
+ }
128
+ }
129
+
130
+ export function getConfiguredBodyLimit(
131
+ environment: NodeJS.ProcessEnv = process.env
132
+ ): number {
133
+ const value =
134
+ environment.BCP_BODY_LIMIT;
135
+
136
+ if (
137
+ value === undefined ||
138
+ value.trim() === ""
139
+ ) {
140
+ return DEFAULT_BODY_LIMIT;
141
+ }
142
+
143
+ const parsed =
144
+ Number(
145
+ value
146
+ );
147
+
148
+ if (
149
+ !Number.isInteger(parsed) ||
150
+ parsed < 1
151
+ ) {
152
+ throw new Error(
153
+ "BCP Framework: BCP_BODY_LIMIT must be a positive integer number of bytes."
154
+ );
155
+ }
156
+
157
+ return parsed;
158
+ }
159
+
160
+ export async function readRequestBodyWithLimit(
161
+ req: http.IncomingMessage,
162
+ limit = getConfiguredBodyLimit()
163
+ ): Promise<Buffer> {
164
+ const contentLength =
165
+ readContentLength(
166
+ req.headers[
167
+ "content-length"
168
+ ]
169
+ );
170
+
171
+ if (
172
+ contentLength !== null &&
173
+ contentLength > limit
174
+ ) {
175
+ req.resume();
176
+ throw new PayloadTooLargeError(
177
+ limit
178
+ );
179
+ }
180
+
181
+ const chunks:
182
+ Buffer[] = [];
183
+ let total = 0;
184
+
185
+ for await (
186
+ const chunk
187
+ of req
188
+ ) {
189
+ const buffer =
190
+ Buffer.isBuffer(
191
+ chunk
192
+ )
193
+ ? chunk
194
+ : Buffer.from(
195
+ chunk
196
+ );
197
+
198
+ total +=
199
+ buffer.length;
200
+
201
+ if (
202
+ total > limit
203
+ ) {
204
+ req.resume();
205
+ throw new PayloadTooLargeError(
206
+ limit
207
+ );
208
+ }
209
+
210
+ chunks.push(
211
+ buffer
212
+ );
213
+ }
214
+
215
+ return Buffer.concat(
216
+ chunks,
217
+ total
218
+ );
219
+ }
220
+
221
+ export function isPayloadTooLargeError(
222
+ error: unknown
223
+ ): error is PayloadTooLargeError {
224
+ return (
225
+ error instanceof
226
+ PayloadTooLargeError ||
227
+ (
228
+ error instanceof Error &&
229
+ error.name ===
230
+ "PayloadTooLargeError"
231
+ )
232
+ );
233
+ }
234
+
235
+ export function assertSafeMiddlewareTarget(
236
+ target: URL,
237
+ originalUrl: URL,
238
+ kind: "redirect" | "rewrite"
239
+ ): void {
240
+ if (
241
+ target.protocol !== "http:" &&
242
+ target.protocol !== "https:"
243
+ ) {
244
+ throw new Error(
245
+ `BCP Framework: middleware ${kind} only supports http: and https: URLs.`
246
+ );
247
+ }
248
+
249
+ if (
250
+ target.username ||
251
+ target.password
252
+ ) {
253
+ throw new Error(
254
+ `BCP Framework: middleware ${kind} URLs must not contain credentials.`
255
+ );
256
+ }
257
+
258
+ if (
259
+ kind === "rewrite" &&
260
+ target.origin !==
261
+ originalUrl.origin
262
+ ) {
263
+ throw new Error(
264
+ "BCP Framework: middleware rewrites must stay on the same origin."
265
+ );
266
+ }
267
+ }
268
+
269
+ function readContentLength(
270
+ value:
271
+ | string
272
+ | string[]
273
+ | undefined
274
+ ): number | null {
275
+ const raw =
276
+ Array.isArray(
277
+ value
278
+ )
279
+ ? value[0]
280
+ : value;
281
+
282
+ if (!raw) {
283
+ return null;
284
+ }
285
+
286
+ const parsed =
287
+ Number(
288
+ raw
289
+ );
290
+
291
+ if (
292
+ !Number.isInteger(parsed) ||
293
+ parsed < 0
294
+ ) {
295
+ return null;
296
+ }
297
+
298
+ return parsed;
299
+ }
300
+
301
+ function readBoolean(
302
+ value: string | undefined,
303
+ fallback: boolean
304
+ ): boolean {
305
+ if (
306
+ value === undefined ||
307
+ value.trim() === ""
308
+ ) {
309
+ return fallback;
310
+ }
311
+
312
+ const normalized =
313
+ value
314
+ .trim()
315
+ .toLowerCase();
316
+
317
+ if (
318
+ normalized === "1" ||
319
+ normalized === "true" ||
320
+ normalized === "yes" ||
321
+ normalized === "on"
322
+ ) {
323
+ return true;
324
+ }
325
+
326
+ if (
327
+ normalized === "0" ||
328
+ normalized === "false" ||
329
+ normalized === "no" ||
330
+ normalized === "off"
331
+ ) {
332
+ return false;
333
+ }
334
+
335
+ return fallback;
336
+ }
337
+
338
+ function readFrameOptions(
339
+ value: string | undefined
340
+ ): "DENY" | "SAMEORIGIN" | null {
341
+ if (
342
+ value === undefined ||
343
+ value.trim() === ""
344
+ ) {
345
+ return "SAMEORIGIN";
346
+ }
347
+
348
+ const normalized =
349
+ value
350
+ .trim()
351
+ .toUpperCase();
352
+
353
+ if (
354
+ normalized === "0" ||
355
+ normalized === "FALSE" ||
356
+ normalized === "OFF"
357
+ ) {
358
+ return null;
359
+ }
360
+
361
+ if (
362
+ normalized === "DENY" ||
363
+ normalized === "SAMEORIGIN"
364
+ ) {
365
+ return normalized;
366
+ }
367
+
368
+ return "SAMEORIGIN";
369
+ }
370
+
371
+ function readRequiredHeader(
372
+ value: string | undefined,
373
+ fallback: string
374
+ ): string {
375
+ const result =
376
+ value?.trim() ||
377
+ fallback;
378
+
379
+ assertHeaderValue(
380
+ result
381
+ );
382
+
383
+ return result;
384
+ }
385
+
386
+ function readOptionalHeader(
387
+ value: string | undefined,
388
+ fallback: string | null = null
389
+ ): string | null {
390
+ if (
391
+ value !== undefined
392
+ ) {
393
+ const normalized =
394
+ value.trim();
395
+
396
+ if (
397
+ normalized === "" ||
398
+ normalized === "0" ||
399
+ normalized.toLowerCase() ===
400
+ "false" ||
401
+ normalized.toLowerCase() ===
402
+ "off"
403
+ ) {
404
+ return null;
405
+ }
406
+
407
+ assertHeaderValue(
408
+ normalized
409
+ );
410
+ return normalized;
411
+ }
412
+
413
+ if (fallback) {
414
+ assertHeaderValue(
415
+ fallback
416
+ );
417
+ }
418
+
419
+ return fallback;
420
+ }
421
+
422
+ function assertHeaderValue(
423
+ value: string
424
+ ): void {
425
+ if (
426
+ value.includes("\r") ||
427
+ value.includes("\n") ||
428
+ value.includes("\0")
429
+ ) {
430
+ throw new Error(
431
+ "BCP Framework: security header values must not contain control characters."
432
+ );
433
+ }
434
+ }
435
+
436
+ function setIfMissing(
437
+ res: http.ServerResponse,
438
+ name: string,
439
+ value: string
440
+ ): void {
441
+ if (
442
+ !res.hasHeader(
443
+ name
444
+ )
445
+ ) {
446
+ res.setHeader(
447
+ name,
448
+ value
449
+ );
450
+ }
451
+ }