@chidchanun/bcp 0.1.22 → 0.1.23

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.
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import fs from "node:fs";
4
+ import path from "node:path";
3
5
  import {
4
6
  spawn,
5
7
  } from "node:child_process";
@@ -7,6 +9,46 @@ import {
7
9
  fileURLToPath,
8
10
  } from "node:url";
9
11
 
12
+ const command =
13
+ process.argv[2] ??
14
+ "dev";
15
+
16
+ const duplicateInstall =
17
+ findDuplicateFrameworkInstall(
18
+ process.cwd()
19
+ );
20
+
21
+ if (duplicateInstall) {
22
+ const message = [
23
+ "BCP Framework: duplicate framework installations detected.",
24
+ "",
25
+ `bcp: ${duplicateInstall.bcp}`,
26
+ `@chidchanun/bcp: ${duplicateInstall.scoped}`,
27
+ "",
28
+ "The application and the BCP CLI can load different framework copies. This breaks shared React contexts such as useLoaderData() and may produce misleading missing-loader errors.",
29
+ "",
30
+ "Keep only one application dependency named bcp. For local tarball verification install it under the bcp dependency key, for example:",
31
+ "npm install --no-save --package-lock=false \"bcp@file:D:\\\\path\\\\to\\\\chidchanun-bcp-0.1.22.tgz\"",
32
+ ].join("\n");
33
+
34
+ if (
35
+ command !== "doctor" &&
36
+ command !== "inspect"
37
+ ) {
38
+ console.error(
39
+ message
40
+ );
41
+ process.exitCode =
42
+ 1;
43
+ process.exit();
44
+ }
45
+
46
+ console.warn(
47
+ message
48
+ );
49
+ console.warn("");
50
+ }
51
+
10
52
  const bootstrapFile =
11
53
  fileURLToPath(
12
54
  new URL(
@@ -91,3 +133,72 @@ child.once(
91
133
  code ?? 1;
92
134
  }
93
135
  );
136
+
137
+ function findDuplicateFrameworkInstall(
138
+ rootDirectory
139
+ ) {
140
+ const bcp =
141
+ resolvePackageRoot(
142
+ path.join(
143
+ rootDirectory,
144
+ "node_modules",
145
+ "bcp"
146
+ )
147
+ );
148
+ const scoped =
149
+ resolvePackageRoot(
150
+ path.join(
151
+ rootDirectory,
152
+ "node_modules",
153
+ "@chidchanun",
154
+ "bcp"
155
+ )
156
+ );
157
+
158
+ if (
159
+ !bcp ||
160
+ !scoped ||
161
+ normalizePath(bcp) ===
162
+ normalizePath(scoped)
163
+ ) {
164
+ return null;
165
+ }
166
+
167
+ return {
168
+ bcp,
169
+ scoped,
170
+ };
171
+ }
172
+
173
+ function resolvePackageRoot(
174
+ directory
175
+ ) {
176
+ if (
177
+ !fs.existsSync(
178
+ path.join(
179
+ directory,
180
+ "package.json"
181
+ )
182
+ )
183
+ ) {
184
+ return null;
185
+ }
186
+
187
+ try {
188
+ return fs.realpathSync(
189
+ directory
190
+ );
191
+ } catch {
192
+ return path.resolve(
193
+ directory
194
+ );
195
+ }
196
+ }
197
+
198
+ function normalizePath(
199
+ value
200
+ ) {
201
+ return process.platform === "win32"
202
+ ? value.toLowerCase()
203
+ : value;
204
+ }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  createContext,
3
+ createElement,
3
4
  useContext,
4
5
  useMemo,
5
6
  useState,
@@ -320,19 +321,25 @@ export function Form({
320
321
  null
321
322
  );
322
323
 
323
- return (
324
- <FormRuntimeContext.Provider
325
- value={runtimeState}
326
- >
327
- <form
328
- {...props}
329
- action={progressiveAction}
330
- method="post"
331
- onSubmit={handleSubmit}
332
- >
333
- {children}
334
- </form>
335
- </FormRuntimeContext.Provider>
324
+ return createElement(
325
+ FormRuntimeContext.Provider,
326
+ {
327
+ value:
328
+ runtimeState,
329
+ },
330
+ createElement(
331
+ "form",
332
+ {
333
+ ...props,
334
+ action:
335
+ progressiveAction,
336
+ method:
337
+ "post",
338
+ onSubmit:
339
+ handleSubmit,
340
+ },
341
+ children
342
+ )
336
343
  );
337
344
  }
338
345
 
@@ -15,6 +15,21 @@ export {
15
15
  type ResponseCookieOptions,
16
16
  } from "../../server/src/request-context.js";
17
17
 
18
+ export {
19
+ attachRequestId,
20
+ createLogger,
21
+ logger,
22
+ requestLogger,
23
+ resolveEnvironmentLogFormat,
24
+ resolveEnvironmentLogLevel,
25
+
26
+ type LogFields,
27
+ type LogFormat,
28
+ type LogLevel,
29
+ type Logger,
30
+ type LoggerOptions,
31
+ } from "../../server/src/logger.js";
32
+
18
33
  export {
19
34
  json,
20
35
  redirect,
@@ -1,5 +1,9 @@
1
1
  import type * as http from "node:http";
2
2
 
3
+ import {
4
+ createLogger,
5
+ } from "./logger.js";
6
+
3
7
  export const DEV_EVENTS_PATH =
4
8
  "/_bcp/dev-events";
5
9
 
@@ -12,11 +16,28 @@ const INTERNAL_API_PATHS =
12
16
  ]);
13
17
 
14
18
  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;
19
+ /^(?:\[bcp\]\s+)?(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\S+)\s+(\d{3})\s+(\d+(?:\.\d+)?)ms$/i;
20
+
21
+ const TIMING_LOG_PATTERN =
22
+ /^(Import|SSR):\s+(\d+(?:\.\d+)?)ms$/i;
16
23
 
17
24
  let requestLogFilterInstalled =
18
25
  false;
19
26
 
27
+ export interface DevHttpRequestLog {
28
+ method: string;
29
+ path: string;
30
+ status: number;
31
+ durationMs: number;
32
+ }
33
+
34
+ export type DevTimingLog = {
35
+ event:
36
+ | "module.import"
37
+ | "ssr.render";
38
+ durationMs: number;
39
+ };
40
+
20
41
  export function shouldLogDevApiPath(
21
42
  pathname: string
22
43
  ): boolean {
@@ -39,20 +60,73 @@ export function shouldLogDevApiPath(
39
60
  );
40
61
  }
41
62
 
42
- export function shouldWriteDevConsoleLine(
63
+ export function parseDevHttpRequestLog(
43
64
  line: string
44
- ): boolean {
65
+ ): DevHttpRequestLog | null {
45
66
  const match =
46
67
  HTTP_REQUEST_LOG_PATTERN.exec(
47
68
  line.trim()
48
69
  );
49
70
 
50
71
  if (!match) {
72
+ return null;
73
+ }
74
+
75
+ return {
76
+ method:
77
+ match[1].toUpperCase(),
78
+ path:
79
+ match[2],
80
+ status:
81
+ Number(
82
+ match[3]
83
+ ),
84
+ durationMs:
85
+ Number(
86
+ match[4]
87
+ ),
88
+ };
89
+ }
90
+
91
+ export function parseDevTimingLog(
92
+ line: string
93
+ ): DevTimingLog | null {
94
+ const match =
95
+ TIMING_LOG_PATTERN.exec(
96
+ line.trim()
97
+ );
98
+
99
+ if (!match) {
100
+ return null;
101
+ }
102
+
103
+ return {
104
+ event:
105
+ match[1].toLowerCase() ===
106
+ "ssr"
107
+ ? "ssr.render"
108
+ : "module.import",
109
+ durationMs:
110
+ Number(
111
+ match[2]
112
+ ),
113
+ };
114
+ }
115
+
116
+ export function shouldWriteDevConsoleLine(
117
+ line: string
118
+ ): boolean {
119
+ const request =
120
+ parseDevHttpRequestLog(
121
+ line
122
+ );
123
+
124
+ if (!request) {
51
125
  return true;
52
126
  }
53
127
 
54
128
  return shouldLogDevApiPath(
55
- match[1]
129
+ request.path
56
130
  );
57
131
  }
58
132
 
@@ -68,18 +142,66 @@ function installRequestLogFilter(): void {
68
142
  console.log.bind(
69
143
  console
70
144
  );
145
+ const devLogger =
146
+ createLogger({
147
+ name:
148
+ "bcp-dev",
149
+ write(
150
+ line
151
+ ) {
152
+ originalLog(
153
+ line
154
+ );
155
+ },
156
+ });
71
157
 
72
158
  console.log = (
73
159
  ...args: unknown[]
74
160
  ) => {
75
161
  if (
76
162
  args.length === 1 &&
77
- typeof args[0] === "string" &&
78
- !shouldWriteDevConsoleLine(
79
- args[0]
80
- )
163
+ typeof args[0] === "string"
81
164
  ) {
82
- return;
165
+ const request =
166
+ parseDevHttpRequestLog(
167
+ args[0]
168
+ );
169
+
170
+ if (request) {
171
+ if (
172
+ shouldLogDevApiPath(
173
+ request.path
174
+ )
175
+ ) {
176
+ devLogger.info(
177
+ "HTTP request",
178
+ {
179
+ event:
180
+ "http.request",
181
+ ...request,
182
+ }
183
+ );
184
+ }
185
+
186
+ return;
187
+ }
188
+
189
+ const timing =
190
+ parseDevTimingLog(
191
+ args[0]
192
+ );
193
+
194
+ if (timing) {
195
+ devLogger.debug(
196
+ timing.event ===
197
+ "ssr.render"
198
+ ? "SSR render"
199
+ : "Module import",
200
+ timing
201
+ );
202
+
203
+ return;
204
+ }
83
205
  }
84
206
 
85
207
  originalLog(