@chidchanun/bcp 0.1.21 → 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.
@@ -71,6 +71,20 @@ switch (cliOptions.command) {
71
71
  break;
72
72
  }
73
73
 
74
+ case "doctor": {
75
+ await runDeveloperCommand(
76
+ "doctor"
77
+ );
78
+ break;
79
+ }
80
+
81
+ case "inspect": {
82
+ await runDeveloperCommand(
83
+ "inspect"
84
+ );
85
+ break;
86
+ }
87
+
74
88
  case "help": {
75
89
  printHelp();
76
90
  break;
@@ -466,6 +480,59 @@ async function runDatabaseCommand() {
466
480
  }
467
481
  }
468
482
 
483
+ async function runDeveloperCommand(
484
+ command:
485
+ "doctor" |
486
+ "inspect"
487
+ ): Promise<void> {
488
+ const rootDirectory =
489
+ resolveProjectRoot(
490
+ cliOptions.rootDirectory
491
+ );
492
+
493
+ const {
494
+ runDoctor,
495
+ runInspect,
496
+ } =
497
+ await import(
498
+ "./developer-tools.js"
499
+ );
500
+
501
+ const options = {
502
+ rootDirectory,
503
+ frameworkVersion:
504
+ FRAMEWORK_VERSION,
505
+ json:
506
+ cliOptions.json,
507
+ };
508
+
509
+ if (
510
+ command === "doctor"
511
+ ) {
512
+ await runDoctor(
513
+ options
514
+ );
515
+ return;
516
+ }
517
+
518
+ try {
519
+ await runInspect(
520
+ options
521
+ );
522
+ } catch (error) {
523
+ if (
524
+ cliOptions.json &&
525
+ error instanceof Error &&
526
+ error.name ===
527
+ "DeveloperToolJsonError"
528
+ ) {
529
+ return;
530
+ }
531
+
532
+ throw error;
533
+ }
534
+ }
535
+
469
536
  function installShutdownHandlers(
470
537
  server: {
471
538
  stop(): Promise<void>;
@@ -642,6 +709,8 @@ Commands:
642
709
  routes Print discovered page and API routes
643
710
  update [target] Update BCP Framework (default target: latest)
644
711
  db <command> Manage database migrations
712
+ doctor Run project/runtime health diagnostics
713
+ inspect Print resolved env, config, dependencies and routes
645
714
  help Show this help message
646
715
  version Show framework version
647
716
 
@@ -652,6 +721,7 @@ Options:
652
721
  --host <host> Alias for --hostname
653
722
  --check Check for a BCP update without changing files
654
723
  --dry-run Preview a BCP update without changing files
724
+ --json JSON output for doctor/inspect
655
725
  -h, --help Show help
656
726
  -v, --version Show version
657
727
 
@@ -664,6 +734,10 @@ Examples:
664
734
  bcp build
665
735
  bcp start
666
736
  bcp routes
737
+ bcp doctor
738
+ bcp doctor --json
739
+ bcp inspect
740
+ bcp inspect --json
667
741
  bcp update
668
742
  bcp update --check
669
743
  bcp db create create_users
@@ -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(