@chidchanun/bcp 0.1.4 → 0.1.6

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.
@@ -6,12 +6,98 @@ export const DEV_EVENTS_PATH =
6
6
  const ENV_RESTART_COOKIE =
7
7
  "__bcp_env_restart";
8
8
 
9
+ const INTERNAL_API_PATHS =
10
+ new Set([
11
+ "/api/bcp-styles",
12
+ ]);
13
+
14
+ const HTTP_REQUEST_LOG_PATTERN =
15
+ /^(?:\[bcp\]\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\S+)\s+\d{3}\s+\d+(?:\.\d+)?ms$/i;
16
+
17
+ let requestLogFilterInstalled =
18
+ false;
19
+
20
+ export function shouldLogDevApiPath(
21
+ pathname: string
22
+ ): boolean {
23
+ const normalized =
24
+ pathname.split(
25
+ "?",
26
+ 1
27
+ )[0];
28
+
29
+ return (
30
+ (
31
+ normalized === "/api" ||
32
+ normalized.startsWith(
33
+ "/api/"
34
+ )
35
+ ) &&
36
+ !INTERNAL_API_PATHS.has(
37
+ normalized
38
+ )
39
+ );
40
+ }
41
+
42
+ export function shouldWriteDevConsoleLine(
43
+ line: string
44
+ ): boolean {
45
+ const match =
46
+ HTTP_REQUEST_LOG_PATTERN.exec(
47
+ line.trim()
48
+ );
49
+
50
+ if (!match) {
51
+ return true;
52
+ }
53
+
54
+ return shouldLogDevApiPath(
55
+ match[1]
56
+ );
57
+ }
58
+
59
+ function installRequestLogFilter(): void {
60
+ if (requestLogFilterInstalled) {
61
+ return;
62
+ }
63
+
64
+ requestLogFilterInstalled =
65
+ true;
66
+
67
+ const originalLog =
68
+ console.log.bind(
69
+ console
70
+ );
71
+
72
+ console.log = (
73
+ ...args: unknown[]
74
+ ) => {
75
+ if (
76
+ args.length === 1 &&
77
+ typeof args[0] === "string" &&
78
+ !shouldWriteDevConsoleLine(
79
+ args[0]
80
+ )
81
+ ) {
82
+ return;
83
+ }
84
+
85
+ originalLog(
86
+ ...args
87
+ );
88
+ };
89
+ }
90
+
9
91
  export class DevHmrServer {
10
92
  private readonly clients =
11
93
  new Set<
12
94
  http.ServerResponse
13
95
  >();
14
96
 
97
+ constructor() {
98
+ installRequestLogFilter();
99
+ }
100
+
15
101
  connect(
16
102
  req: http.IncomingMessage,
17
103
  res: http.ServerResponse
@@ -5,6 +5,10 @@ import type {
5
5
  DevServerOptions,
6
6
  } from "./index.js";
7
7
 
8
+ import {
9
+ createCriticalCssProxy,
10
+ } from "./critical-css-proxy.js";
11
+
8
12
  import {
9
13
  loadProjectMiddleware,
10
14
  } from "./middleware-loader.js";
@@ -56,6 +60,12 @@ export function createMiddlewareDevServer(
56
60
  > | null =
57
61
  null;
58
62
 
63
+ let criticalCssGateway:
64
+ ReturnType<
65
+ typeof createCriticalCssProxy
66
+ > | null =
67
+ null;
68
+
59
69
  let started =
60
70
  false;
61
71
 
@@ -68,6 +78,8 @@ export function createMiddlewareDevServer(
68
78
  await findFreePort();
69
79
  const middlewarePort =
70
80
  await findFreePort();
81
+ const securityPort =
82
+ await findFreePort();
71
83
 
72
84
  internalServer =
73
85
  createStaticDevServer({
@@ -111,8 +123,10 @@ export function createMiddlewareDevServer(
111
123
 
112
124
  securityGateway =
113
125
  createSecurityProxy({
114
- port,
115
- hostname,
126
+ port:
127
+ securityPort,
128
+ hostname:
129
+ "127.0.0.1",
116
130
  upstreamPort:
117
131
  middlewarePort,
118
132
  upstreamHostname:
@@ -133,12 +147,48 @@ export function createMiddlewareDevServer(
133
147
  throw error;
134
148
  }
135
149
 
150
+ criticalCssGateway =
151
+ createCriticalCssProxy({
152
+ port,
153
+ hostname,
154
+ upstreamPort:
155
+ securityPort,
156
+ upstreamHostname:
157
+ "127.0.0.1",
158
+ cssFile:
159
+ path.join(
160
+ rootDirectory,
161
+ "public",
162
+ "bcp.css"
163
+ ),
164
+ });
165
+
166
+ try {
167
+ await criticalCssGateway.start();
168
+ } catch (error) {
169
+ await securityGateway.stop();
170
+ await middlewareGateway.stop();
171
+ await internalServer.stop();
172
+ internalServer =
173
+ null;
174
+ middlewareGateway =
175
+ null;
176
+ securityGateway =
177
+ null;
178
+ criticalCssGateway =
179
+ null;
180
+ throw error;
181
+ }
182
+
136
183
  started =
137
184
  true;
138
185
 
139
186
  console.log(
140
187
  `[BCP Security] Dev gateway: http://${hostname}:${port}`
141
188
  );
189
+ console.log(
190
+ `[BCP CSS] Inline /bcp.css when <= 8 KiB and allowed by CSP.`
191
+ );
142
192
  console.log(
143
193
  `[BCP Middleware] File: ${path.join(rootDirectory, "middleware.ts")} (optional; .tsx/.js/.jsx also supported)`
144
194
  );
@@ -146,6 +196,12 @@ export function createMiddlewareDevServer(
146
196
  }
147
197
 
148
198
  async function stop(): Promise<void> {
199
+ if (criticalCssGateway) {
200
+ await criticalCssGateway.stop();
201
+ criticalCssGateway =
202
+ null;
203
+ }
204
+
149
205
  if (securityGateway) {
150
206
  await securityGateway.stop();
151
207
  securityGateway =
@@ -257,4 +313,4 @@ async function findFreePort(): Promise<number> {
257
313
  );
258
314
  }
259
315
  }
260
- }
316
+ }
@@ -0,0 +1,205 @@
1
+ import * as net from "node:net";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ createCriticalCssProxy,
6
+ } from "./critical-css-proxy.js";
7
+
8
+ import {
9
+ createStandaloneProductionServer as createBaseStandaloneProductionServer,
10
+ type StandaloneApiRoute,
11
+ type StandalonePageRoute,
12
+ type StandaloneProductionServerOptions as BaseStandaloneProductionServerOptions,
13
+ } from "./standalone-production-runtime-v5.js";
14
+
15
+ export type {
16
+ StandaloneApiRoute,
17
+ StandalonePageRoute,
18
+ };
19
+
20
+ export interface StandaloneProductionServerOptions
21
+ extends BaseStandaloneProductionServerOptions {}
22
+
23
+ export function createStandaloneProductionServer(
24
+ options: StandaloneProductionServerOptions
25
+ ) {
26
+ let baseServer:
27
+ ReturnType<
28
+ typeof createBaseStandaloneProductionServer
29
+ > | null =
30
+ null;
31
+
32
+ let criticalCssProxy:
33
+ ReturnType<
34
+ typeof createCriticalCssProxy
35
+ > | null =
36
+ null;
37
+
38
+ let started =
39
+ false;
40
+
41
+ async function start(): Promise<void> {
42
+ if (started) {
43
+ return;
44
+ }
45
+
46
+ const internalPort =
47
+ await findFreePort();
48
+
49
+ baseServer =
50
+ createBaseStandaloneProductionServer({
51
+ ...options,
52
+ port:
53
+ internalPort,
54
+ hostname:
55
+ "127.0.0.1",
56
+ });
57
+
58
+ await baseServer.start();
59
+
60
+ criticalCssProxy =
61
+ createCriticalCssProxy({
62
+ port:
63
+ options.port,
64
+ hostname:
65
+ options.hostname,
66
+ upstreamPort:
67
+ internalPort,
68
+ upstreamHostname:
69
+ "127.0.0.1",
70
+ cssFile:
71
+ path.join(
72
+ options.buildDirectory,
73
+ "public",
74
+ "bcp.css"
75
+ ),
76
+ });
77
+
78
+ try {
79
+ await criticalCssProxy.start();
80
+ } catch (error) {
81
+ await baseServer.stop();
82
+ baseServer =
83
+ null;
84
+ criticalCssProxy =
85
+ null;
86
+ throw error;
87
+ }
88
+
89
+ started =
90
+ true;
91
+
92
+ console.log(
93
+ `[BCP CSS] Inline /bcp.css when <= 8 KiB.`
94
+ );
95
+ console.log("");
96
+ }
97
+
98
+ async function stop(): Promise<void> {
99
+ if (criticalCssProxy) {
100
+ await criticalCssProxy.stop();
101
+ criticalCssProxy =
102
+ null;
103
+ }
104
+
105
+ if (baseServer) {
106
+ await baseServer.stop();
107
+ baseServer =
108
+ null;
109
+ }
110
+
111
+ started =
112
+ false;
113
+ }
114
+
115
+ return {
116
+ start,
117
+ stop,
118
+ };
119
+ }
120
+
121
+ async function findFreePort(): Promise<number> {
122
+ const server =
123
+ net.createServer();
124
+
125
+ try {
126
+ await new Promise<void>(
127
+ (
128
+ resolve,
129
+ reject
130
+ ) => {
131
+ const onError =
132
+ (error: Error) => {
133
+ server.off(
134
+ "listening",
135
+ onListening
136
+ );
137
+ reject(
138
+ error
139
+ );
140
+ };
141
+
142
+ const onListening =
143
+ () => {
144
+ server.off(
145
+ "error",
146
+ onError
147
+ );
148
+ resolve();
149
+ };
150
+
151
+ server.once(
152
+ "error",
153
+ onError
154
+ );
155
+ server.once(
156
+ "listening",
157
+ onListening
158
+ );
159
+ server.listen(
160
+ 0,
161
+ "127.0.0.1"
162
+ );
163
+ }
164
+ );
165
+
166
+ const address =
167
+ server.address();
168
+
169
+ if (
170
+ !address ||
171
+ typeof address ===
172
+ "string"
173
+ ) {
174
+ throw new Error(
175
+ "BCP Framework: could not allocate critical CSS production port."
176
+ );
177
+ }
178
+
179
+ return address.port;
180
+ } finally {
181
+ if (
182
+ server.listening
183
+ ) {
184
+ await new Promise<void>(
185
+ (
186
+ resolve,
187
+ reject
188
+ ) => {
189
+ server.close(
190
+ (error) => {
191
+ if (error) {
192
+ reject(
193
+ error
194
+ );
195
+ return;
196
+ }
197
+
198
+ resolve();
199
+ }
200
+ );
201
+ }
202
+ );
203
+ }
204
+ }
205
+ }
@@ -3,4 +3,4 @@ export {
3
3
  type StandaloneApiRoute,
4
4
  type StandalonePageRoute,
5
5
  type StandaloneProductionServerOptions,
6
- } from "./standalone-production-runtime-v5.js";
6
+ } from "./standalone-production-runtime-v6.js";