@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.
- package/README.md +38 -2
- package/docs/README.md +67 -11
- package/docs/developer-tools.md +166 -0
- package/docs/development-logging.md +234 -6
- package/docs/releases/0.1.22.md +142 -0
- package/docs/releases/0.1.23.md +131 -0
- package/package.json +3 -2
- package/packages/cli/bin/bcp.mjs +111 -0
- package/packages/cli/src/args.ts +32 -0
- package/packages/cli/src/developer-tools.ts +1249 -0
- package/packages/cli/src/index.ts +74 -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/server/src/production-server.ts +0 -1746
- package/packages/server/src/standalone-production-runtime.ts +0 -1951
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import {
|
|
2
|
+
requestId,
|
|
3
|
+
requestMethod,
|
|
4
|
+
requestUrl,
|
|
5
|
+
} from "./request-context.js";
|
|
6
|
+
|
|
7
|
+
export type LogLevel =
|
|
8
|
+
| "debug"
|
|
9
|
+
| "info"
|
|
10
|
+
| "warn"
|
|
11
|
+
| "error"
|
|
12
|
+
| "silent";
|
|
13
|
+
|
|
14
|
+
export type LogFormat =
|
|
15
|
+
| "pretty"
|
|
16
|
+
| "json";
|
|
17
|
+
|
|
18
|
+
export type LogFields =
|
|
19
|
+
Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export interface LoggerOptions {
|
|
22
|
+
name?: string;
|
|
23
|
+
level?: LogLevel;
|
|
24
|
+
format?: LogFormat;
|
|
25
|
+
bindings?: LogFields;
|
|
26
|
+
write?: (
|
|
27
|
+
line: string,
|
|
28
|
+
level: Exclude<LogLevel, "silent">
|
|
29
|
+
) => void;
|
|
30
|
+
now?: () => Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Logger {
|
|
34
|
+
debug(
|
|
35
|
+
message: string,
|
|
36
|
+
fields?: LogFields
|
|
37
|
+
): void;
|
|
38
|
+
info(
|
|
39
|
+
message: string,
|
|
40
|
+
fields?: LogFields
|
|
41
|
+
): void;
|
|
42
|
+
warn(
|
|
43
|
+
message: string,
|
|
44
|
+
fields?: LogFields
|
|
45
|
+
): void;
|
|
46
|
+
error(
|
|
47
|
+
message: string,
|
|
48
|
+
fields?: LogFields
|
|
49
|
+
): void;
|
|
50
|
+
child(
|
|
51
|
+
bindings: LogFields
|
|
52
|
+
): Logger;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ResolvedLoggerOptions {
|
|
56
|
+
name: string;
|
|
57
|
+
level?: LogLevel;
|
|
58
|
+
format?: LogFormat;
|
|
59
|
+
bindings: LogFields;
|
|
60
|
+
write?: LoggerOptions["write"];
|
|
61
|
+
now: () => Date;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const LOG_LEVEL_WEIGHT:
|
|
65
|
+
Record<LogLevel, number> = {
|
|
66
|
+
debug: 10,
|
|
67
|
+
info: 20,
|
|
68
|
+
warn: 30,
|
|
69
|
+
error: 40,
|
|
70
|
+
silent: Number.POSITIVE_INFINITY,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function createLogger(
|
|
74
|
+
options: LoggerOptions = {}
|
|
75
|
+
): Logger {
|
|
76
|
+
const resolved:
|
|
77
|
+
ResolvedLoggerOptions = {
|
|
78
|
+
name:
|
|
79
|
+
normalizeLoggerName(
|
|
80
|
+
options.name
|
|
81
|
+
) ??
|
|
82
|
+
"bcp",
|
|
83
|
+
level:
|
|
84
|
+
options.level,
|
|
85
|
+
format:
|
|
86
|
+
options.format,
|
|
87
|
+
bindings: {
|
|
88
|
+
...options.bindings,
|
|
89
|
+
},
|
|
90
|
+
write:
|
|
91
|
+
options.write,
|
|
92
|
+
now:
|
|
93
|
+
options.now ??
|
|
94
|
+
(() => new Date()),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return createLoggerInstance(
|
|
98
|
+
resolved
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const logger =
|
|
103
|
+
createLogger();
|
|
104
|
+
|
|
105
|
+
export async function requestLogger(
|
|
106
|
+
bindings: LogFields = {}
|
|
107
|
+
): Promise<Logger> {
|
|
108
|
+
const [
|
|
109
|
+
id,
|
|
110
|
+
method,
|
|
111
|
+
url,
|
|
112
|
+
] = await Promise.all([
|
|
113
|
+
requestId(),
|
|
114
|
+
requestMethod(),
|
|
115
|
+
requestUrl(),
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
return logger.child({
|
|
119
|
+
...bindings,
|
|
120
|
+
requestId:
|
|
121
|
+
id,
|
|
122
|
+
method,
|
|
123
|
+
path:
|
|
124
|
+
`${url.pathname}${url.search}`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function attachRequestId(
|
|
129
|
+
response: Response
|
|
130
|
+
): Promise<Response> {
|
|
131
|
+
const id =
|
|
132
|
+
await requestId();
|
|
133
|
+
const responseHeaders =
|
|
134
|
+
new Headers(
|
|
135
|
+
response.headers
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
responseHeaders.set(
|
|
139
|
+
"x-request-id",
|
|
140
|
+
id
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return new Response(
|
|
144
|
+
response.body,
|
|
145
|
+
{
|
|
146
|
+
status:
|
|
147
|
+
response.status,
|
|
148
|
+
statusText:
|
|
149
|
+
response.statusText,
|
|
150
|
+
headers:
|
|
151
|
+
responseHeaders,
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createLoggerInstance(
|
|
157
|
+
options: ResolvedLoggerOptions
|
|
158
|
+
): Logger {
|
|
159
|
+
const write = (
|
|
160
|
+
level: Exclude<LogLevel, "silent">,
|
|
161
|
+
message: string,
|
|
162
|
+
fields: LogFields = {}
|
|
163
|
+
): void => {
|
|
164
|
+
const configuredLevel =
|
|
165
|
+
options.level ??
|
|
166
|
+
resolveEnvironmentLogLevel();
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
LOG_LEVEL_WEIGHT[level] <
|
|
170
|
+
LOG_LEVEL_WEIGHT[configuredLevel]
|
|
171
|
+
) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const record = {
|
|
176
|
+
timestamp:
|
|
177
|
+
options.now()
|
|
178
|
+
.toISOString(),
|
|
179
|
+
level,
|
|
180
|
+
logger:
|
|
181
|
+
options.name,
|
|
182
|
+
message,
|
|
183
|
+
...options.bindings,
|
|
184
|
+
...fields,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const format =
|
|
188
|
+
options.format ??
|
|
189
|
+
resolveEnvironmentLogFormat();
|
|
190
|
+
const line =
|
|
191
|
+
format === "json"
|
|
192
|
+
? stringifyLogRecord(
|
|
193
|
+
record
|
|
194
|
+
)
|
|
195
|
+
: formatPrettyLogRecord(
|
|
196
|
+
record
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (options.write) {
|
|
200
|
+
options.write(
|
|
201
|
+
line,
|
|
202
|
+
level
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
writeConsoleLine(
|
|
208
|
+
line,
|
|
209
|
+
level
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
debug(
|
|
215
|
+
message,
|
|
216
|
+
fields
|
|
217
|
+
) {
|
|
218
|
+
write(
|
|
219
|
+
"debug",
|
|
220
|
+
message,
|
|
221
|
+
fields
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
info(
|
|
225
|
+
message,
|
|
226
|
+
fields
|
|
227
|
+
) {
|
|
228
|
+
write(
|
|
229
|
+
"info",
|
|
230
|
+
message,
|
|
231
|
+
fields
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
warn(
|
|
235
|
+
message,
|
|
236
|
+
fields
|
|
237
|
+
) {
|
|
238
|
+
write(
|
|
239
|
+
"warn",
|
|
240
|
+
message,
|
|
241
|
+
fields
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
error(
|
|
245
|
+
message,
|
|
246
|
+
fields
|
|
247
|
+
) {
|
|
248
|
+
write(
|
|
249
|
+
"error",
|
|
250
|
+
message,
|
|
251
|
+
fields
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
child(
|
|
255
|
+
bindings
|
|
256
|
+
) {
|
|
257
|
+
return createLoggerInstance({
|
|
258
|
+
...options,
|
|
259
|
+
bindings: {
|
|
260
|
+
...options.bindings,
|
|
261
|
+
...bindings,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function resolveEnvironmentLogLevel():
|
|
269
|
+
LogLevel {
|
|
270
|
+
return normalizeLogLevel(
|
|
271
|
+
process.env.BCP_LOG_LEVEL
|
|
272
|
+
) ??
|
|
273
|
+
"info";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function resolveEnvironmentLogFormat():
|
|
277
|
+
LogFormat {
|
|
278
|
+
return normalizeLogFormat(
|
|
279
|
+
process.env.BCP_LOG_FORMAT
|
|
280
|
+
) ??
|
|
281
|
+
"pretty";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function normalizeLogLevel(
|
|
285
|
+
value:
|
|
286
|
+
string | undefined
|
|
287
|
+
): LogLevel | null {
|
|
288
|
+
if (!value) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const normalized =
|
|
293
|
+
value
|
|
294
|
+
.trim()
|
|
295
|
+
.toLowerCase();
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
normalized === "debug" ||
|
|
299
|
+
normalized === "info" ||
|
|
300
|
+
normalized === "warn" ||
|
|
301
|
+
normalized === "error" ||
|
|
302
|
+
normalized === "silent"
|
|
303
|
+
)
|
|
304
|
+
? normalized
|
|
305
|
+
: null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeLogFormat(
|
|
309
|
+
value:
|
|
310
|
+
string | undefined
|
|
311
|
+
): LogFormat | null {
|
|
312
|
+
if (!value) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const normalized =
|
|
317
|
+
value
|
|
318
|
+
.trim()
|
|
319
|
+
.toLowerCase();
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
normalized === "pretty" ||
|
|
323
|
+
normalized === "json"
|
|
324
|
+
)
|
|
325
|
+
? normalized
|
|
326
|
+
: null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function normalizeLoggerName(
|
|
330
|
+
value:
|
|
331
|
+
string | undefined
|
|
332
|
+
): string | null {
|
|
333
|
+
if (!value) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const normalized =
|
|
338
|
+
value.trim();
|
|
339
|
+
|
|
340
|
+
return normalized.length > 0
|
|
341
|
+
? normalized
|
|
342
|
+
: null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function formatPrettyLogRecord(
|
|
346
|
+
record: Record<string, unknown>
|
|
347
|
+
): string {
|
|
348
|
+
const {
|
|
349
|
+
timestamp,
|
|
350
|
+
level,
|
|
351
|
+
logger: loggerName,
|
|
352
|
+
message,
|
|
353
|
+
...fields
|
|
354
|
+
} = record;
|
|
355
|
+
const suffix =
|
|
356
|
+
Object.keys(fields).length > 0
|
|
357
|
+
? " " +
|
|
358
|
+
Object.entries(fields)
|
|
359
|
+
.map(
|
|
360
|
+
([key, value]) =>
|
|
361
|
+
`${key}=${formatPrettyValue(value)}`
|
|
362
|
+
)
|
|
363
|
+
.join(" ")
|
|
364
|
+
: "";
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
`[${String(timestamp)}] ` +
|
|
368
|
+
`[${String(level).toUpperCase()}] ` +
|
|
369
|
+
`[${String(loggerName)}] ` +
|
|
370
|
+
`${String(message)}${suffix}`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function formatPrettyValue(
|
|
375
|
+
value: unknown
|
|
376
|
+
): string {
|
|
377
|
+
if (
|
|
378
|
+
typeof value === "string"
|
|
379
|
+
) {
|
|
380
|
+
return /\s/.test(value)
|
|
381
|
+
? (
|
|
382
|
+
JSON.stringify(
|
|
383
|
+
value
|
|
384
|
+
) ??
|
|
385
|
+
"\"\""
|
|
386
|
+
)
|
|
387
|
+
: value;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
typeof value === "number" ||
|
|
392
|
+
typeof value === "boolean" ||
|
|
393
|
+
value === null ||
|
|
394
|
+
value === undefined
|
|
395
|
+
) {
|
|
396
|
+
return String(value);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return stringifyLogRecord(
|
|
400
|
+
value
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function stringifyLogRecord(
|
|
405
|
+
value: unknown
|
|
406
|
+
): string {
|
|
407
|
+
const seen =
|
|
408
|
+
new WeakSet<object>();
|
|
409
|
+
const serialized =
|
|
410
|
+
JSON.stringify(
|
|
411
|
+
value,
|
|
412
|
+
(_key, item) => {
|
|
413
|
+
if (
|
|
414
|
+
item instanceof Error
|
|
415
|
+
) {
|
|
416
|
+
return {
|
|
417
|
+
name:
|
|
418
|
+
item.name,
|
|
419
|
+
message:
|
|
420
|
+
item.message,
|
|
421
|
+
stack:
|
|
422
|
+
item.stack,
|
|
423
|
+
cause:
|
|
424
|
+
item.cause,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
typeof item === "bigint"
|
|
430
|
+
) {
|
|
431
|
+
return item.toString();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (
|
|
435
|
+
item !== null &&
|
|
436
|
+
typeof item === "object"
|
|
437
|
+
) {
|
|
438
|
+
if (seen.has(item)) {
|
|
439
|
+
return "[Circular]";
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
seen.add(item);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return item;
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
return serialized ??
|
|
450
|
+
"undefined";
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function writeConsoleLine(
|
|
454
|
+
line: string,
|
|
455
|
+
level: Exclude<LogLevel, "silent">
|
|
456
|
+
): void {
|
|
457
|
+
if (level === "error") {
|
|
458
|
+
console.error(
|
|
459
|
+
line
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (level === "warn") {
|
|
465
|
+
console.warn(
|
|
466
|
+
line
|
|
467
|
+
);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
console.log(
|
|
472
|
+
line
|
|
473
|
+
);
|
|
474
|
+
}
|