@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,478 @@
1
+ import type {
2
+ GenerateMetadata,
3
+ Metadata,
4
+ MetadataIcons,
5
+ MetadataOpenGraph,
6
+ MetadataRobots,
7
+ ResolvedMetadata,
8
+ } from "../../client/src/metadata.js";
9
+
10
+ export interface MetadataSource {
11
+ label: string;
12
+ metadata?: Metadata;
13
+ generateMetadata?: GenerateMetadata;
14
+ }
15
+
16
+ export async function resolveRouteMetadata(
17
+ sources: MetadataSource[],
18
+ params: Record<string, string>,
19
+ fallbackTitle = "BCP Framework"
20
+ ): Promise<ResolvedMetadata> {
21
+ let merged: Metadata = {};
22
+
23
+ for (const source of sources) {
24
+ const hasStatic =
25
+ source.metadata !== undefined;
26
+ const hasGenerated =
27
+ source.generateMetadata !== undefined;
28
+
29
+ if (hasStatic && hasGenerated) {
30
+ throw new Error(
31
+ `BCP Framework: ${source.label} cannot export both metadata and generateMetadata.`
32
+ );
33
+ }
34
+
35
+ let next: Metadata | undefined;
36
+
37
+ if (hasGenerated) {
38
+ if (
39
+ typeof source.generateMetadata !==
40
+ "function"
41
+ ) {
42
+ throw new Error(
43
+ `BCP Framework: generateMetadata in ${source.label} must be a function.`
44
+ );
45
+ }
46
+
47
+ next =
48
+ await source.generateMetadata({
49
+ params,
50
+ parent:
51
+ resolveMetadata(
52
+ merged,
53
+ fallbackTitle
54
+ ),
55
+ });
56
+ } else if (hasStatic) {
57
+ next =
58
+ source.metadata;
59
+ }
60
+
61
+ if (next === undefined) {
62
+ continue;
63
+ }
64
+
65
+ assertMetadata(
66
+ next,
67
+ source.label
68
+ );
69
+
70
+ merged =
71
+ mergeMetadata(
72
+ merged,
73
+ next
74
+ );
75
+ }
76
+
77
+ return resolveMetadata(
78
+ merged,
79
+ fallbackTitle
80
+ );
81
+ }
82
+
83
+ function mergeMetadata(
84
+ base: Metadata,
85
+ next: Metadata
86
+ ): Metadata {
87
+ return {
88
+ ...base,
89
+ ...next,
90
+ alternates:
91
+ next.alternates
92
+ ? {
93
+ ...base.alternates,
94
+ ...next.alternates,
95
+ }
96
+ : base.alternates,
97
+ openGraph:
98
+ next.openGraph
99
+ ? {
100
+ ...base.openGraph,
101
+ ...next.openGraph,
102
+ }
103
+ : base.openGraph,
104
+ robots:
105
+ mergeRobots(
106
+ base.robots,
107
+ next.robots
108
+ ),
109
+ icons:
110
+ mergeIcons(
111
+ base.icons,
112
+ next.icons
113
+ ),
114
+ };
115
+ }
116
+
117
+ function mergeRobots(
118
+ base: Metadata["robots"],
119
+ next: Metadata["robots"]
120
+ ): Metadata["robots"] {
121
+ if (next === undefined) {
122
+ return base;
123
+ }
124
+
125
+ if (
126
+ typeof next === "string" ||
127
+ typeof base === "string" ||
128
+ base === undefined
129
+ ) {
130
+ return next;
131
+ }
132
+
133
+ return {
134
+ ...base,
135
+ ...next,
136
+ };
137
+ }
138
+
139
+ function mergeIcons(
140
+ base: Metadata["icons"],
141
+ next: Metadata["icons"]
142
+ ): Metadata["icons"] {
143
+ if (next === undefined) {
144
+ return base;
145
+ }
146
+
147
+ const normalizedNext =
148
+ normalizeIcons(
149
+ next
150
+ );
151
+
152
+ const normalizedBase =
153
+ base === undefined
154
+ ? undefined
155
+ : normalizeIcons(
156
+ base
157
+ );
158
+
159
+ return {
160
+ ...normalizedBase,
161
+ ...normalizedNext,
162
+ };
163
+ }
164
+
165
+ function resolveMetadata(
166
+ metadata: Metadata,
167
+ fallbackTitle: string
168
+ ): ResolvedMetadata {
169
+ const title =
170
+ metadata.title ??
171
+ fallbackTitle;
172
+
173
+ const description =
174
+ metadata.description ??
175
+ null;
176
+
177
+ const openGraph =
178
+ metadata.openGraph
179
+ ? resolveOpenGraph(
180
+ metadata.openGraph,
181
+ title,
182
+ description
183
+ )
184
+ : null;
185
+
186
+ const icons =
187
+ normalizeIcons(
188
+ metadata.icons
189
+ );
190
+
191
+ return {
192
+ title,
193
+ description,
194
+ robots:
195
+ resolveRobots(
196
+ metadata.robots
197
+ ),
198
+ canonical:
199
+ metadata.alternates
200
+ ?.canonical ??
201
+ null,
202
+ openGraph,
203
+ icons: {
204
+ icon:
205
+ icons.icon ??
206
+ null,
207
+ shortcut:
208
+ icons.shortcut ??
209
+ null,
210
+ apple:
211
+ icons.apple ??
212
+ null,
213
+ },
214
+ };
215
+ }
216
+
217
+ function resolveOpenGraph(
218
+ value: MetadataOpenGraph,
219
+ title: string,
220
+ description: string | null
221
+ ): ResolvedMetadata["openGraph"] {
222
+ const images =
223
+ value.images === undefined
224
+ ? []
225
+ : Array.isArray(
226
+ value.images
227
+ )
228
+ ? value.images
229
+ : [
230
+ value.images,
231
+ ];
232
+
233
+ return {
234
+ title:
235
+ value.title ??
236
+ title,
237
+ description:
238
+ value.description ??
239
+ description,
240
+ url:
241
+ value.url ??
242
+ null,
243
+ type:
244
+ value.type ??
245
+ null,
246
+ siteName:
247
+ value.siteName ??
248
+ null,
249
+ images,
250
+ };
251
+ }
252
+
253
+ function resolveRobots(
254
+ value: Metadata["robots"]
255
+ ): string | null {
256
+ if (value === undefined) {
257
+ return null;
258
+ }
259
+
260
+ if (typeof value === "string") {
261
+ return value;
262
+ }
263
+
264
+ const directives:
265
+ string[] = [];
266
+
267
+ pushBooleanDirective(
268
+ directives,
269
+ value.index,
270
+ "index",
271
+ "noindex"
272
+ );
273
+ pushBooleanDirective(
274
+ directives,
275
+ value.follow,
276
+ "follow",
277
+ "nofollow"
278
+ );
279
+
280
+ if (value.noarchive) {
281
+ directives.push(
282
+ "noarchive"
283
+ );
284
+ }
285
+
286
+ if (value.nosnippet) {
287
+ directives.push(
288
+ "nosnippet"
289
+ );
290
+ }
291
+
292
+ if (value.noimageindex) {
293
+ directives.push(
294
+ "noimageindex"
295
+ );
296
+ }
297
+
298
+ return directives.length > 0
299
+ ? directives.join(", ")
300
+ : null;
301
+ }
302
+
303
+ function pushBooleanDirective(
304
+ directives: string[],
305
+ value: boolean | undefined,
306
+ positive: string,
307
+ negative: string
308
+ ): void {
309
+ if (value === undefined) {
310
+ return;
311
+ }
312
+
313
+ directives.push(
314
+ value
315
+ ? positive
316
+ : negative
317
+ );
318
+ }
319
+
320
+ function normalizeIcons(
321
+ value: Metadata["icons"]
322
+ ): MetadataIcons {
323
+ if (value === undefined) {
324
+ return {};
325
+ }
326
+
327
+ if (typeof value === "string") {
328
+ return {
329
+ icon: value,
330
+ };
331
+ }
332
+
333
+ return value;
334
+ }
335
+
336
+ function assertMetadata(
337
+ value: Metadata,
338
+ label: string
339
+ ): void {
340
+ if (
341
+ typeof value !== "object" ||
342
+ value === null ||
343
+ Array.isArray(value)
344
+ ) {
345
+ throw new Error(
346
+ `BCP Framework: metadata in ${label} must be an object.`
347
+ );
348
+ }
349
+
350
+ assertOptionalString(
351
+ value.title,
352
+ "title",
353
+ label
354
+ );
355
+ assertOptionalString(
356
+ value.description,
357
+ "description",
358
+ label
359
+ );
360
+
361
+ if (
362
+ value.robots !== undefined &&
363
+ typeof value.robots !== "string"
364
+ ) {
365
+ assertRobots(
366
+ value.robots,
367
+ label
368
+ );
369
+ }
370
+
371
+ if (value.alternates) {
372
+ assertOptionalString(
373
+ value.alternates.canonical,
374
+ "alternates.canonical",
375
+ label
376
+ );
377
+ }
378
+
379
+ if (value.openGraph) {
380
+ for (
381
+ const [name, item]
382
+ of Object.entries({
383
+ title:
384
+ value.openGraph.title,
385
+ description:
386
+ value.openGraph.description,
387
+ url:
388
+ value.openGraph.url,
389
+ type:
390
+ value.openGraph.type,
391
+ siteName:
392
+ value.openGraph.siteName,
393
+ })
394
+ ) {
395
+ assertOptionalString(
396
+ item,
397
+ `openGraph.${name}`,
398
+ label
399
+ );
400
+ }
401
+
402
+ const images =
403
+ value.openGraph.images;
404
+
405
+ if (
406
+ images !== undefined &&
407
+ !(
408
+ typeof images === "string" ||
409
+ (
410
+ Array.isArray(images) &&
411
+ images.every(
412
+ (image) =>
413
+ typeof image ===
414
+ "string"
415
+ )
416
+ )
417
+ )
418
+ ) {
419
+ throw new Error(
420
+ `BCP Framework: metadata.openGraph.images in ${label} must be a string or string array.`
421
+ );
422
+ }
423
+ }
424
+
425
+ if (
426
+ value.icons !== undefined &&
427
+ typeof value.icons !== "string"
428
+ ) {
429
+ for (
430
+ const [name, item]
431
+ of Object.entries(
432
+ value.icons
433
+ )
434
+ ) {
435
+ assertOptionalString(
436
+ item,
437
+ `icons.${name}`,
438
+ label
439
+ );
440
+ }
441
+ }
442
+ }
443
+
444
+ function assertRobots(
445
+ value: MetadataRobots,
446
+ label: string
447
+ ): void {
448
+ for (
449
+ const [name, item]
450
+ of Object.entries(
451
+ value
452
+ )
453
+ ) {
454
+ if (
455
+ item !== undefined &&
456
+ typeof item !== "boolean"
457
+ ) {
458
+ throw new Error(
459
+ `BCP Framework: metadata.robots.${name} in ${label} must be a boolean.`
460
+ );
461
+ }
462
+ }
463
+ }
464
+
465
+ function assertOptionalString(
466
+ value: unknown,
467
+ field: string,
468
+ label: string
469
+ ): void {
470
+ if (
471
+ value !== undefined &&
472
+ typeof value !== "string"
473
+ ) {
474
+ throw new Error(
475
+ `BCP Framework: metadata.${field} in ${label} must be a string.`
476
+ );
477
+ }
478
+ }
@@ -0,0 +1,260 @@
1
+ import * as net from "node:net";
2
+ import path from "node:path";
3
+
4
+ import type {
5
+ DevServerOptions,
6
+ } from "./index.js";
7
+
8
+ import {
9
+ loadProjectMiddleware,
10
+ } from "./middleware-loader.js";
11
+
12
+ import {
13
+ createMiddlewareProxy,
14
+ } from "./middleware-proxy.js";
15
+
16
+ import {
17
+ createSecurityProxy,
18
+ } from "./security-proxy.js";
19
+
20
+ import {
21
+ createStaticDevServer,
22
+ } from "./static-dev-server.js";
23
+
24
+ export function createMiddlewareDevServer(
25
+ options: DevServerOptions = {}
26
+ ) {
27
+ const port =
28
+ options.port ??
29
+ 3000;
30
+
31
+ const hostname =
32
+ options.hostname ??
33
+ "localhost";
34
+
35
+ const rootDirectory =
36
+ path.resolve(
37
+ options.rootDirectory ??
38
+ process.cwd()
39
+ );
40
+
41
+ let internalServer:
42
+ ReturnType<
43
+ typeof createStaticDevServer
44
+ > | null =
45
+ null;
46
+
47
+ let middlewareGateway:
48
+ ReturnType<
49
+ typeof createMiddlewareProxy
50
+ > | null =
51
+ null;
52
+
53
+ let securityGateway:
54
+ ReturnType<
55
+ typeof createSecurityProxy
56
+ > | null =
57
+ null;
58
+
59
+ let started =
60
+ false;
61
+
62
+ async function start(): Promise<void> {
63
+ if (started) {
64
+ return;
65
+ }
66
+
67
+ const applicationPort =
68
+ await findFreePort();
69
+ const middlewarePort =
70
+ await findFreePort();
71
+
72
+ internalServer =
73
+ createStaticDevServer({
74
+ ...options,
75
+ rootDirectory,
76
+ port:
77
+ applicationPort,
78
+ hostname:
79
+ "127.0.0.1",
80
+ });
81
+
82
+ await internalServer.start();
83
+
84
+ middlewareGateway =
85
+ createMiddlewareProxy({
86
+ port:
87
+ middlewarePort,
88
+ hostname:
89
+ "127.0.0.1",
90
+ upstreamPort:
91
+ applicationPort,
92
+ upstreamHostname:
93
+ "127.0.0.1",
94
+ getMiddleware:
95
+ () =>
96
+ loadProjectMiddleware(
97
+ rootDirectory
98
+ ),
99
+ });
100
+
101
+ try {
102
+ await middlewareGateway.start();
103
+ } catch (error) {
104
+ await internalServer.stop();
105
+ internalServer =
106
+ null;
107
+ middlewareGateway =
108
+ null;
109
+ throw error;
110
+ }
111
+
112
+ securityGateway =
113
+ createSecurityProxy({
114
+ port,
115
+ hostname,
116
+ upstreamPort:
117
+ middlewarePort,
118
+ upstreamHostname:
119
+ "127.0.0.1",
120
+ });
121
+
122
+ try {
123
+ await securityGateway.start();
124
+ } catch (error) {
125
+ await middlewareGateway.stop();
126
+ await internalServer.stop();
127
+ internalServer =
128
+ null;
129
+ middlewareGateway =
130
+ null;
131
+ securityGateway =
132
+ null;
133
+ throw error;
134
+ }
135
+
136
+ started =
137
+ true;
138
+
139
+ console.log(
140
+ `[BCP Security] Dev gateway: http://${hostname}:${port}`
141
+ );
142
+ console.log(
143
+ `[BCP Middleware] File: ${path.join(rootDirectory, "middleware.ts")} (optional; .tsx/.js/.jsx also supported)`
144
+ );
145
+ console.log("");
146
+ }
147
+
148
+ async function stop(): Promise<void> {
149
+ if (securityGateway) {
150
+ await securityGateway.stop();
151
+ securityGateway =
152
+ null;
153
+ }
154
+
155
+ if (middlewareGateway) {
156
+ await middlewareGateway.stop();
157
+ middlewareGateway =
158
+ null;
159
+ }
160
+
161
+ if (internalServer) {
162
+ await internalServer.stop();
163
+ internalServer =
164
+ null;
165
+ }
166
+
167
+ started =
168
+ false;
169
+ }
170
+
171
+ return {
172
+ start,
173
+ stop,
174
+ };
175
+ }
176
+
177
+ async function findFreePort(): Promise<number> {
178
+ const server =
179
+ net.createServer();
180
+
181
+ try {
182
+ await new Promise<void>(
183
+ (
184
+ resolve,
185
+ reject
186
+ ) => {
187
+ const onError =
188
+ (error: Error) => {
189
+ server.off(
190
+ "listening",
191
+ onListening
192
+ );
193
+ reject(
194
+ error
195
+ );
196
+ };
197
+
198
+ const onListening =
199
+ () => {
200
+ server.off(
201
+ "error",
202
+ onError
203
+ );
204
+ resolve();
205
+ };
206
+
207
+ server.once(
208
+ "error",
209
+ onError
210
+ );
211
+ server.once(
212
+ "listening",
213
+ onListening
214
+ );
215
+ server.listen(
216
+ 0,
217
+ "127.0.0.1"
218
+ );
219
+ }
220
+ );
221
+
222
+ const address =
223
+ server.address();
224
+
225
+ if (
226
+ !address ||
227
+ typeof address ===
228
+ "string"
229
+ ) {
230
+ throw new Error(
231
+ "BCP Framework: could not allocate middleware dev port."
232
+ );
233
+ }
234
+
235
+ return address.port;
236
+ } finally {
237
+ if (
238
+ server.listening
239
+ ) {
240
+ await new Promise<void>(
241
+ (
242
+ resolve,
243
+ reject
244
+ ) => {
245
+ server.close(
246
+ (error) => {
247
+ if (error) {
248
+ reject(
249
+ error
250
+ );
251
+ return;
252
+ }
253
+ resolve();
254
+ }
255
+ );
256
+ }
257
+ );
258
+ }
259
+ }
260
+ }