@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,504 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ pathToFileURL,
5
+ } from "node:url";
6
+
7
+ import {
8
+ readResolvedBcpConfig,
9
+ } from "../../config/src/index.js";
10
+
11
+ import {
12
+ assertNoPageApiConflicts,
13
+ scanApiRoutes,
14
+ scanRoutes,
15
+ } from "../../router/src/index.js";
16
+
17
+ import {
18
+ parseCliArgs,
19
+ resolveProjectRoot,
20
+ } from "./args.js";
21
+
22
+ import {
23
+ getFrameworkVersion,
24
+ } from "./version.js";
25
+
26
+ const FRAMEWORK_VERSION =
27
+ getFrameworkVersion();
28
+
29
+ const cliOptions =
30
+ parseCliArgs(
31
+ process.argv.slice(2)
32
+ );
33
+
34
+ const frameworkConfig =
35
+ readResolvedBcpConfig();
36
+
37
+ switch (cliOptions.command) {
38
+ case "dev": {
39
+ await runDev();
40
+ break;
41
+ }
42
+
43
+ case "build": {
44
+ await runBuild();
45
+ break;
46
+ }
47
+
48
+ case "start": {
49
+ await runStart();
50
+ break;
51
+ }
52
+
53
+ case "routes": {
54
+ runRoutes();
55
+ break;
56
+ }
57
+
58
+ case "help": {
59
+ printHelp();
60
+ break;
61
+ }
62
+
63
+ case "version": {
64
+ console.log(
65
+ `BCP Framework v${FRAMEWORK_VERSION}`
66
+ );
67
+ break;
68
+ }
69
+ }
70
+
71
+ async function runDev() {
72
+ process.env.NODE_ENV =
73
+ "development";
74
+
75
+ const {
76
+ createMiddlewareDevServer,
77
+ } =
78
+ await import(
79
+ "../../server/src/middleware-dev-server.js"
80
+ );
81
+
82
+ const rootDirectory =
83
+ resolveProjectRoot(
84
+ cliOptions.rootDirectory
85
+ );
86
+
87
+ console.log(
88
+ `[BCP CLI] Project: ${rootDirectory}`
89
+ );
90
+
91
+ const server =
92
+ createMiddlewareDevServer({
93
+ port:
94
+ frameworkConfig.server.port,
95
+ hostname:
96
+ frameworkConfig.server.hostname,
97
+ rootDirectory,
98
+ });
99
+
100
+ installShutdownHandlers(
101
+ server
102
+ );
103
+
104
+ await server.start();
105
+ }
106
+
107
+ async function runBuild() {
108
+ process.env.NODE_ENV =
109
+ "production";
110
+
111
+ const [
112
+ {
113
+ buildProductionClient,
114
+ },
115
+ {
116
+ buildProductionServer,
117
+ },
118
+ ] =
119
+ await Promise.all([
120
+ import(
121
+ "../../bundler/src/partial-hydration.js"
122
+ ),
123
+ import(
124
+ "../../bundler/src/server-production-middleware.js"
125
+ ),
126
+ ]);
127
+
128
+ const rootDirectory =
129
+ resolveProjectRoot(
130
+ cliOptions.rootDirectory
131
+ );
132
+
133
+ const appDirectory =
134
+ path.join(
135
+ rootDirectory,
136
+ "app"
137
+ );
138
+
139
+ const pageRoutes =
140
+ scanRoutes(
141
+ appDirectory
142
+ );
143
+
144
+ const apiRoutes =
145
+ scanApiRoutes(
146
+ appDirectory
147
+ );
148
+
149
+ assertNoPageApiConflicts(
150
+ pageRoutes,
151
+ apiRoutes
152
+ );
153
+
154
+ console.log("");
155
+ console.log(
156
+ `[BCP Build] Project: ${rootDirectory}`
157
+ );
158
+ console.log(
159
+ `[BCP Build] Pages: ${pageRoutes.length} | API: ${apiRoutes.length}`
160
+ );
161
+ console.log(
162
+ `[BCP Build] Config: minify=${frameworkConfig.build.minify} sourceMaps=${frameworkConfig.build.sourceMaps} compression=${frameworkConfig.compression}`
163
+ );
164
+
165
+ const startedAt =
166
+ performance.now();
167
+
168
+ const manifest =
169
+ await buildProductionClient(
170
+ pageRoutes,
171
+ rootDirectory
172
+ );
173
+
174
+ const serverBuild =
175
+ await buildProductionServer(
176
+ pageRoutes,
177
+ apiRoutes,
178
+ rootDirectory,
179
+ manifest
180
+ );
181
+
182
+ const duration =
183
+ performance.now() -
184
+ startedAt;
185
+
186
+ console.log("");
187
+
188
+ for (
189
+ const [
190
+ pathname,
191
+ routeAsset,
192
+ ]
193
+ of Object.entries(
194
+ manifest.routes
195
+ )
196
+ ) {
197
+ console.log(
198
+ ` ${pathname} -> ${routeAsset.entry}`
199
+ );
200
+
201
+ console.log(
202
+ ` imports: ${routeAsset.imports.length} | raw ${formatBytes(routeAsset.size.raw)} | gzip ${formatBytes(routeAsset.size.gzip)} | br ${formatBytes(routeAsset.size.brotli)}`
203
+ );
204
+ }
205
+
206
+ const totalSize =
207
+ manifest.assets.reduce(
208
+ (
209
+ total,
210
+ asset
211
+ ) => ({
212
+ raw:
213
+ total.raw +
214
+ asset.size.raw,
215
+ gzip:
216
+ total.gzip +
217
+ asset.size.gzip,
218
+ brotli:
219
+ total.brotli +
220
+ asset.size.brotli,
221
+ }),
222
+ {
223
+ raw: 0,
224
+ gzip: 0,
225
+ brotli: 0,
226
+ }
227
+ );
228
+
229
+ console.log("");
230
+ console.log(
231
+ `[BCP Build] Client assets: raw ${formatBytes(totalSize.raw)} | gzip ${formatBytes(totalSize.gzip)} | br ${formatBytes(totalSize.brotli)}`
232
+ );
233
+ console.log(
234
+ `[BCP Build] Server bundle: ${formatBytes(serverBuild.size)}`
235
+ );
236
+ console.log(
237
+ `[BCP Build] Server: ${serverBuild.serverFile}`
238
+ );
239
+ console.log(
240
+ `[BCP Build] Build ID: ${manifest.buildId}`
241
+ );
242
+ console.log(
243
+ `[BCP Build] Assets: ${manifest.assets.length}`
244
+ );
245
+ console.log(
246
+ `[BCP Build] Completed in ${duration.toFixed(2)}ms`
247
+ );
248
+ console.log("");
249
+ console.log(
250
+ "[BCP Build] Standalone run:"
251
+ );
252
+ console.log(
253
+ ` node ${serverBuild.serverFile}`
254
+ );
255
+ console.log("");
256
+ }
257
+
258
+ async function runStart() {
259
+ process.env.NODE_ENV =
260
+ "production";
261
+
262
+ const rootDirectory =
263
+ resolveProjectRoot(
264
+ cliOptions.rootDirectory
265
+ );
266
+
267
+ const serverFile =
268
+ path.join(
269
+ rootDirectory,
270
+ ".bcp-framework",
271
+ "build",
272
+ "server",
273
+ "server.mjs"
274
+ );
275
+
276
+ if (
277
+ !fs.existsSync(
278
+ serverFile
279
+ )
280
+ ) {
281
+ throw new Error(
282
+ "BCP Framework: standalone production build was not found. Run `bcp build` before `bcp start`."
283
+ );
284
+ }
285
+
286
+ /*
287
+ * Keep build-time configuration frozen in server.mjs/config.json.
288
+ * Only explicit runtime environment values and CLI options should
289
+ * override the standalone artifact after it has been built.
290
+ */
291
+ if (
292
+ cliOptions.port !==
293
+ undefined
294
+ ) {
295
+ process.env.BCP_PORT =
296
+ String(
297
+ cliOptions.port
298
+ );
299
+ }
300
+
301
+ if (
302
+ cliOptions.hostname !==
303
+ undefined
304
+ ) {
305
+ process.env.BCP_HOSTNAME =
306
+ cliOptions.hostname;
307
+ }
308
+
309
+ console.log(
310
+ `[BCP CLI] Starting standalone build: ${serverFile}`
311
+ );
312
+
313
+ if (
314
+ cliOptions.hostname !== undefined ||
315
+ cliOptions.port !== undefined
316
+ ) {
317
+ console.log(
318
+ `[BCP CLI] Runtime server override: ${cliOptions.hostname ?? process.env.BCP_HOSTNAME ?? "(frozen hostname)"}:${cliOptions.port ?? process.env.BCP_PORT ?? "(frozen port)"}`
319
+ );
320
+ }
321
+
322
+ await import(
323
+ pathToFileURL(
324
+ serverFile
325
+ ).href
326
+ );
327
+ }
328
+
329
+ function installShutdownHandlers(
330
+ server: {
331
+ stop(): Promise<void>;
332
+ }
333
+ ) {
334
+ process.once(
335
+ "SIGINT",
336
+ async () => {
337
+ console.log("");
338
+ console.log(
339
+ "Stopping BCP Framework..."
340
+ );
341
+
342
+ await server.stop();
343
+
344
+ process.exit(0);
345
+ }
346
+ );
347
+
348
+ process.once(
349
+ "SIGTERM",
350
+ async () => {
351
+ await server.stop();
352
+
353
+ process.exit(0);
354
+ }
355
+ );
356
+ }
357
+
358
+ function runRoutes() {
359
+ const rootDirectory =
360
+ resolveProjectRoot(
361
+ cliOptions.rootDirectory
362
+ );
363
+
364
+ const appDirectory =
365
+ path.join(
366
+ rootDirectory,
367
+ "app"
368
+ );
369
+
370
+ const pageRoutes =
371
+ scanRoutes(
372
+ appDirectory
373
+ );
374
+
375
+ const apiRoutes =
376
+ scanApiRoutes(
377
+ appDirectory
378
+ );
379
+
380
+ assertNoPageApiConflicts(
381
+ pageRoutes,
382
+ apiRoutes
383
+ );
384
+
385
+ console.log("");
386
+ console.log(
387
+ `BCP Framework Routes: ${rootDirectory}`
388
+ );
389
+
390
+ console.log("");
391
+ console.log(
392
+ "Pages:"
393
+ );
394
+
395
+ if (
396
+ pageRoutes.length === 0
397
+ ) {
398
+ console.log(
399
+ " (none)"
400
+ );
401
+ }
402
+
403
+ for (
404
+ const route of pageRoutes
405
+ ) {
406
+ console.log(
407
+ ` ${route.pathname}`
408
+ );
409
+
410
+ console.log(
411
+ ` ${route.filePath}`
412
+ );
413
+ }
414
+
415
+ console.log("");
416
+ console.log(
417
+ "API:"
418
+ );
419
+
420
+ if (
421
+ apiRoutes.length === 0
422
+ ) {
423
+ console.log(
424
+ " (none)"
425
+ );
426
+ }
427
+
428
+ for (
429
+ const route of apiRoutes
430
+ ) {
431
+ console.log(
432
+ ` ${route.pathname}`
433
+ );
434
+
435
+ console.log(
436
+ ` ${route.filePath}`
437
+ );
438
+ }
439
+
440
+ console.log("");
441
+ console.log(
442
+ `Total: ${pageRoutes.length} page(s), ${apiRoutes.length} API route(s)`
443
+ );
444
+ }
445
+
446
+ function formatBytes(
447
+ value: number
448
+ ): string {
449
+ if (
450
+ value < 1024
451
+ ) {
452
+ return `${value} B`;
453
+ }
454
+
455
+ if (
456
+ value < 1024 * 1024
457
+ ) {
458
+ return `${(
459
+ value / 1024
460
+ ).toFixed(1)} KiB`;
461
+ }
462
+
463
+ return `${(
464
+ value /
465
+ (1024 * 1024)
466
+ ).toFixed(2)} MiB`;
467
+ }
468
+
469
+ function printHelp() {
470
+ console.log(`
471
+ BCP Framework v${FRAMEWORK_VERSION}
472
+
473
+ Usage:
474
+ bcp <command> [options]
475
+
476
+ Commands:
477
+ dev Start the development server
478
+ build Create optimized client assets and standalone server.mjs
479
+ start Start the standalone production build
480
+ routes Print discovered page and API routes
481
+ help Show this help message
482
+ version Show framework version
483
+
484
+ Options:
485
+ --root <path> Project root directory
486
+ --port <number> Override configured HTTP port
487
+ --hostname <host> Override configured HTTP hostname
488
+ --host <host> Alias for --hostname
489
+ -h, --help Show help
490
+ -v, --version Show version
491
+
492
+ Config precedence:
493
+ dev/build: CLI > BCP_* environment > bcp.config.ts > defaults
494
+ start: CLI > runtime BCP_* environment > frozen build config
495
+
496
+ Examples:
497
+ bcp dev
498
+ bcp dev --port 4000
499
+ bcp build
500
+ bcp start
501
+ bcp start --port 4000
502
+ bcp routes
503
+ `);
504
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ fileURLToPath,
5
+ } from "node:url";
6
+
7
+ export function getFrameworkVersion(): string {
8
+ const currentDirectory =
9
+ path.dirname(
10
+ fileURLToPath(
11
+ import.meta.url
12
+ )
13
+ );
14
+
15
+ const packageFile =
16
+ path.resolve(
17
+ currentDirectory,
18
+ "../../../package.json"
19
+ );
20
+
21
+ try {
22
+ const packageJson =
23
+ JSON.parse(
24
+ fs.readFileSync(
25
+ packageFile,
26
+ "utf8"
27
+ )
28
+ ) as {
29
+ version?: unknown;
30
+ };
31
+
32
+ if (
33
+ typeof packageJson.version ===
34
+ "string" &&
35
+ packageJson.version.trim() !==
36
+ ""
37
+ ) {
38
+ return packageJson.version;
39
+ }
40
+ } catch {
41
+ // Fall through to a non-release development marker.
42
+ }
43
+
44
+ return "0.0.0-dev";
45
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ cache,
3
+ clearCache,
4
+ dedupe,
5
+ getCacheStats,
6
+ revalidatePath,
7
+ revalidateTag,
8
+ type CacheOptions,
9
+ type CacheStats,
10
+ type RevalidateValue,
11
+ } from "../../cache/src/index.js";
@@ -0,0 +1,18 @@
1
+ export {
2
+ applyResolvedBcpConfig,
3
+ defaultBcpConfig,
4
+ defineConfig,
5
+ getConfigFileNames,
6
+ loadBcpConfig,
7
+ readResolvedBcpConfig,
8
+ resolveBcpConfig,
9
+ type BcpBuildConfig,
10
+ type BcpCacheConfig,
11
+ type BcpConfig,
12
+ type BcpExperimentalConfig,
13
+ type BcpSecurityConfig,
14
+ type BcpServerConfig,
15
+ type LoadedBcpConfig,
16
+ type ResolveBcpConfigOverrides,
17
+ type ResolvedBcpConfig,
18
+ } from "../../config/src/index.js";
@@ -0,0 +1,149 @@
1
+ import {
2
+ Component,
3
+ createElement,
4
+ type ComponentType,
5
+ type ErrorInfo,
6
+ type ReactNode,
7
+ } from "react";
8
+
9
+ import {
10
+ isNotFoundError,
11
+ } from "./not-found.js";
12
+
13
+ export interface BcpErrorComponentProps {
14
+ error: Error;
15
+ reset: () => void;
16
+ }
17
+
18
+ export interface BcpRouteErrorBoundaryProps {
19
+ children: ReactNode;
20
+ ErrorComponent?:
21
+ ComponentType<
22
+ BcpErrorComponentProps
23
+ > | null;
24
+ NotFoundComponent?:
25
+ ComponentType | null;
26
+ }
27
+
28
+ interface BcpRouteErrorBoundaryState {
29
+ error: unknown | null;
30
+ }
31
+
32
+ export class BcpRouteErrorBoundary
33
+ extends Component<
34
+ BcpRouteErrorBoundaryProps,
35
+ BcpRouteErrorBoundaryState
36
+ > {
37
+ state:
38
+ BcpRouteErrorBoundaryState = {
39
+ error: null,
40
+ };
41
+
42
+ static getDerivedStateFromError(
43
+ error: unknown
44
+ ):
45
+ BcpRouteErrorBoundaryState {
46
+ return {
47
+ error,
48
+ };
49
+ }
50
+
51
+ componentDidCatch(
52
+ error: Error,
53
+ info: ErrorInfo
54
+ ): void {
55
+ console.error(
56
+ "[BCP Route Error]",
57
+ error,
58
+ info.componentStack
59
+ );
60
+ }
61
+
62
+ private reset = (): void => {
63
+ this.setState({
64
+ error: null,
65
+ });
66
+ };
67
+
68
+ render(): ReactNode {
69
+ const {
70
+ error,
71
+ } = this.state;
72
+
73
+ if (error === null) {
74
+ return this.props.children;
75
+ }
76
+
77
+ if (
78
+ isNotFoundError(
79
+ error
80
+ )
81
+ ) {
82
+ const NotFoundComponent =
83
+ this.props
84
+ .NotFoundComponent;
85
+
86
+ if (NotFoundComponent) {
87
+ return createElement(
88
+ NotFoundComponent
89
+ );
90
+ }
91
+
92
+ return createElement(
93
+ "main",
94
+ null,
95
+ createElement(
96
+ "h1",
97
+ null,
98
+ "404"
99
+ ),
100
+ createElement(
101
+ "p",
102
+ null,
103
+ "Page Not Found"
104
+ )
105
+ );
106
+ }
107
+
108
+ const ErrorComponent =
109
+ this.props.ErrorComponent;
110
+
111
+ const normalizedError =
112
+ error instanceof Error
113
+ ? error
114
+ : new Error(
115
+ "An unexpected client error occurred."
116
+ );
117
+
118
+ if (ErrorComponent) {
119
+ return createElement(
120
+ ErrorComponent,
121
+ {
122
+ error:
123
+ normalizedError,
124
+ reset:
125
+ this.reset,
126
+ }
127
+ );
128
+ }
129
+
130
+ return createElement(
131
+ "main",
132
+ null,
133
+ createElement(
134
+ "h1",
135
+ null,
136
+ "Something went wrong"
137
+ ),
138
+ createElement(
139
+ "button",
140
+ {
141
+ type: "button",
142
+ onClick:
143
+ this.reset,
144
+ },
145
+ "Try again"
146
+ )
147
+ );
148
+ }
149
+ }
@@ -0,0 +1,3 @@
1
+ export type HydrationMode =
2
+ | "full"
3
+ | "none";