@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.
- package/docs/README.md +41 -9
- package/docs/developer-tools.md +48 -0
- package/docs/development-logging.md +234 -6
- package/docs/releases/0.1.22.md +66 -1
- package/docs/releases/0.1.23.md +131 -0
- package/package.json +3 -2
- package/packages/cli/bin/bcp.mjs +111 -0
- package/packages/client/src/form.tsx +20 -13
- package/packages/client/src/server.ts +15 -0
- package/packages/server/src/dev-hmr.ts +131 -9
- package/packages/server/src/logger.ts +474 -0
package/packages/cli/bin/bcp.mjs
CHANGED
|
@@ -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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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+)?(
|
|
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
|
|
63
|
+
export function parseDevHttpRequestLog(
|
|
43
64
|
line: string
|
|
44
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
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(
|