@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,1249 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
createRequire,
|
|
5
|
+
} from "node:module";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
validateClientBoundaries,
|
|
9
|
+
} from "../../bundler/src/client-boundary.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveBcpConfig,
|
|
12
|
+
type ResolvedBcpConfig,
|
|
13
|
+
} from "../../config/src/index.js";
|
|
14
|
+
import {
|
|
15
|
+
loadEnvironment,
|
|
16
|
+
} from "../../env/src/index.js";
|
|
17
|
+
import {
|
|
18
|
+
assertNoPageApiConflicts,
|
|
19
|
+
scanApiRoutes,
|
|
20
|
+
scanRoutes,
|
|
21
|
+
} from "../../router/src/index.js";
|
|
22
|
+
|
|
23
|
+
export type DoctorStatus =
|
|
24
|
+
| "pass"
|
|
25
|
+
| "warn"
|
|
26
|
+
| "fail";
|
|
27
|
+
|
|
28
|
+
export interface DoctorCheck {
|
|
29
|
+
id: string;
|
|
30
|
+
status: DoctorStatus;
|
|
31
|
+
label: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DoctorReport {
|
|
36
|
+
frameworkVersion: string;
|
|
37
|
+
rootDirectory: string;
|
|
38
|
+
runtime: {
|
|
39
|
+
node: string;
|
|
40
|
+
platform: NodeJS.Platform;
|
|
41
|
+
arch: string;
|
|
42
|
+
};
|
|
43
|
+
checks: DoctorCheck[];
|
|
44
|
+
summary: {
|
|
45
|
+
passed: number;
|
|
46
|
+
warnings: number;
|
|
47
|
+
failed: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface InspectReport {
|
|
52
|
+
frameworkVersion: string;
|
|
53
|
+
rootDirectory: string;
|
|
54
|
+
runtime: {
|
|
55
|
+
node: string;
|
|
56
|
+
platform: NodeJS.Platform;
|
|
57
|
+
arch: string;
|
|
58
|
+
};
|
|
59
|
+
environment: {
|
|
60
|
+
mode: "development";
|
|
61
|
+
files: string[];
|
|
62
|
+
publicVariables: string[];
|
|
63
|
+
};
|
|
64
|
+
config: {
|
|
65
|
+
file: string | null;
|
|
66
|
+
resolved: ResolvedBcpConfig;
|
|
67
|
+
};
|
|
68
|
+
dependencies: {
|
|
69
|
+
bcp: string | null;
|
|
70
|
+
react: string | null;
|
|
71
|
+
reactDom: string | null;
|
|
72
|
+
};
|
|
73
|
+
routes: {
|
|
74
|
+
pages: Array<{
|
|
75
|
+
pathname: string;
|
|
76
|
+
file: string;
|
|
77
|
+
}>;
|
|
78
|
+
api: Array<{
|
|
79
|
+
pathname: string;
|
|
80
|
+
file: string;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface DeveloperToolOptions {
|
|
86
|
+
rootDirectory: string;
|
|
87
|
+
frameworkVersion: string;
|
|
88
|
+
json?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface PackageManifestInfo {
|
|
92
|
+
version: string;
|
|
93
|
+
manifestPath: string;
|
|
94
|
+
packageRoot: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ProjectPackageJson {
|
|
98
|
+
dependencies?: Record<string, string>;
|
|
99
|
+
devDependencies?: Record<string, string>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const MINIMUM_NODE_VERSION =
|
|
103
|
+
"24.11.0";
|
|
104
|
+
|
|
105
|
+
const frameworkRequire =
|
|
106
|
+
createRequire(
|
|
107
|
+
import.meta.url
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export async function runDoctor(
|
|
111
|
+
options: DeveloperToolOptions
|
|
112
|
+
): Promise<DoctorReport> {
|
|
113
|
+
const report =
|
|
114
|
+
await collectDoctorReport(
|
|
115
|
+
options
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (options.json) {
|
|
119
|
+
console.log(
|
|
120
|
+
JSON.stringify(
|
|
121
|
+
report,
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
printDoctorReport(
|
|
128
|
+
report
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
report.summary.failed > 0
|
|
134
|
+
) {
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return report;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function runInspect(
|
|
142
|
+
options: DeveloperToolOptions
|
|
143
|
+
): Promise<InspectReport> {
|
|
144
|
+
try {
|
|
145
|
+
const report =
|
|
146
|
+
await collectInspectReport(
|
|
147
|
+
options
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (options.json) {
|
|
151
|
+
console.log(
|
|
152
|
+
JSON.stringify(
|
|
153
|
+
report,
|
|
154
|
+
null,
|
|
155
|
+
2
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
printInspectReport(
|
|
160
|
+
report
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return report;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (options.json) {
|
|
167
|
+
console.log(
|
|
168
|
+
JSON.stringify(
|
|
169
|
+
{
|
|
170
|
+
frameworkVersion:
|
|
171
|
+
options.frameworkVersion,
|
|
172
|
+
rootDirectory:
|
|
173
|
+
options.rootDirectory,
|
|
174
|
+
error:
|
|
175
|
+
errorMessage(
|
|
176
|
+
error
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
throw new DeveloperToolJsonError();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function collectDoctorReport(
|
|
192
|
+
options: DeveloperToolOptions
|
|
193
|
+
): Promise<DoctorReport> {
|
|
194
|
+
const rootDirectory =
|
|
195
|
+
path.resolve(
|
|
196
|
+
options.rootDirectory
|
|
197
|
+
);
|
|
198
|
+
const checks:
|
|
199
|
+
DoctorCheck[] = [];
|
|
200
|
+
|
|
201
|
+
addCheck(
|
|
202
|
+
checks,
|
|
203
|
+
"runtime.node",
|
|
204
|
+
compareVersions(
|
|
205
|
+
process.versions.node,
|
|
206
|
+
MINIMUM_NODE_VERSION
|
|
207
|
+
) >= 0
|
|
208
|
+
? "pass"
|
|
209
|
+
: "fail",
|
|
210
|
+
"Node.js runtime",
|
|
211
|
+
compareVersions(
|
|
212
|
+
process.versions.node,
|
|
213
|
+
MINIMUM_NODE_VERSION
|
|
214
|
+
) >= 0
|
|
215
|
+
? `Node.js ${process.versions.node} satisfies >=${MINIMUM_NODE_VERSION}.`
|
|
216
|
+
: `Node.js ${process.versions.node} is unsupported; BCP requires >=${MINIMUM_NODE_VERSION}.`
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const packageFile =
|
|
220
|
+
path.join(
|
|
221
|
+
rootDirectory,
|
|
222
|
+
"package.json"
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
let projectPackage:
|
|
226
|
+
ProjectPackageJson | null = null;
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
!fs.existsSync(
|
|
230
|
+
packageFile
|
|
231
|
+
)
|
|
232
|
+
) {
|
|
233
|
+
addCheck(
|
|
234
|
+
checks,
|
|
235
|
+
"project.package",
|
|
236
|
+
"fail",
|
|
237
|
+
"Project package",
|
|
238
|
+
"package.json was not found in the project root."
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
try {
|
|
242
|
+
projectPackage =
|
|
243
|
+
readProjectPackage(
|
|
244
|
+
packageFile
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
addCheck(
|
|
248
|
+
checks,
|
|
249
|
+
"project.package",
|
|
250
|
+
"pass",
|
|
251
|
+
"Project package",
|
|
252
|
+
"package.json is readable."
|
|
253
|
+
);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
addCheck(
|
|
256
|
+
checks,
|
|
257
|
+
"project.package",
|
|
258
|
+
"fail",
|
|
259
|
+
"Project package",
|
|
260
|
+
errorMessage(
|
|
261
|
+
error
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const appDirectory =
|
|
268
|
+
path.join(
|
|
269
|
+
rootDirectory,
|
|
270
|
+
"app"
|
|
271
|
+
);
|
|
272
|
+
const hasAppDirectory =
|
|
273
|
+
isDirectory(
|
|
274
|
+
appDirectory
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
addCheck(
|
|
278
|
+
checks,
|
|
279
|
+
"project.app",
|
|
280
|
+
hasAppDirectory
|
|
281
|
+
? "pass"
|
|
282
|
+
: "fail",
|
|
283
|
+
"App directory",
|
|
284
|
+
hasAppDirectory
|
|
285
|
+
? "app/ is present."
|
|
286
|
+
: "app/ was not found. BCP page and API routes are discovered from this directory."
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const publicDirectory =
|
|
290
|
+
path.join(
|
|
291
|
+
rootDirectory,
|
|
292
|
+
"public"
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
addCheck(
|
|
296
|
+
checks,
|
|
297
|
+
"project.public",
|
|
298
|
+
isDirectory(
|
|
299
|
+
publicDirectory
|
|
300
|
+
)
|
|
301
|
+
? "pass"
|
|
302
|
+
: "warn",
|
|
303
|
+
"Public directory",
|
|
304
|
+
isDirectory(
|
|
305
|
+
publicDirectory
|
|
306
|
+
)
|
|
307
|
+
? "public/ is present."
|
|
308
|
+
: "public/ is not present. This is valid when the application has no static assets."
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (projectPackage) {
|
|
312
|
+
const declared =
|
|
313
|
+
findDeclaredFrameworkDependency(
|
|
314
|
+
projectPackage
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
declared?.name === "bcp"
|
|
319
|
+
) {
|
|
320
|
+
addCheck(
|
|
321
|
+
checks,
|
|
322
|
+
"dependencies.bcp",
|
|
323
|
+
"pass",
|
|
324
|
+
"BCP dependency",
|
|
325
|
+
`bcp is declared as ${declared.range}.`
|
|
326
|
+
);
|
|
327
|
+
} else if (declared) {
|
|
328
|
+
addCheck(
|
|
329
|
+
checks,
|
|
330
|
+
"dependencies.bcp",
|
|
331
|
+
"warn",
|
|
332
|
+
"BCP dependency",
|
|
333
|
+
`${declared.name} is declared as ${declared.range}. The documented application import name is bcp.`
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
addCheck(
|
|
337
|
+
checks,
|
|
338
|
+
"dependencies.bcp",
|
|
339
|
+
"fail",
|
|
340
|
+
"BCP dependency",
|
|
341
|
+
"Neither bcp nor @chidchanun/bcp is declared in dependencies/devDependencies."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const appReact =
|
|
347
|
+
resolveInstalledPackage(
|
|
348
|
+
packageFile,
|
|
349
|
+
"react"
|
|
350
|
+
);
|
|
351
|
+
const appReactDom =
|
|
352
|
+
resolveInstalledPackage(
|
|
353
|
+
packageFile,
|
|
354
|
+
"react-dom"
|
|
355
|
+
);
|
|
356
|
+
const appBcp =
|
|
357
|
+
resolveInstalledPackage(
|
|
358
|
+
packageFile,
|
|
359
|
+
"bcp"
|
|
360
|
+
) ??
|
|
361
|
+
resolveInstalledPackage(
|
|
362
|
+
packageFile,
|
|
363
|
+
"@chidchanun/bcp"
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
addInstalledPackageCheck(
|
|
367
|
+
checks,
|
|
368
|
+
"dependencies.installed-bcp",
|
|
369
|
+
"Installed BCP package",
|
|
370
|
+
appBcp
|
|
371
|
+
);
|
|
372
|
+
addInstalledPackageCheck(
|
|
373
|
+
checks,
|
|
374
|
+
"dependencies.react",
|
|
375
|
+
"React package",
|
|
376
|
+
appReact
|
|
377
|
+
);
|
|
378
|
+
addInstalledPackageCheck(
|
|
379
|
+
checks,
|
|
380
|
+
"dependencies.react-dom",
|
|
381
|
+
"React DOM package",
|
|
382
|
+
appReactDom
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
appReact &&
|
|
387
|
+
appReactDom
|
|
388
|
+
) {
|
|
389
|
+
addCheck(
|
|
390
|
+
checks,
|
|
391
|
+
"dependencies.react-version-parity",
|
|
392
|
+
appReact.version ===
|
|
393
|
+
appReactDom.version
|
|
394
|
+
? "pass"
|
|
395
|
+
: "fail",
|
|
396
|
+
"React renderer version parity",
|
|
397
|
+
appReact.version ===
|
|
398
|
+
appReactDom.version
|
|
399
|
+
? `react and react-dom both resolve to ${appReact.version}.`
|
|
400
|
+
: `react resolves to ${appReact.version} while react-dom resolves to ${appReactDom.version}. Install matching versions.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const frameworkReact =
|
|
405
|
+
resolveFrameworkPackage(
|
|
406
|
+
"react"
|
|
407
|
+
);
|
|
408
|
+
const frameworkReactDom =
|
|
409
|
+
resolveFrameworkPackage(
|
|
410
|
+
"react-dom"
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
if (
|
|
414
|
+
appReact &&
|
|
415
|
+
frameworkReact
|
|
416
|
+
) {
|
|
417
|
+
const sameReact =
|
|
418
|
+
samePackageRoot(
|
|
419
|
+
appReact,
|
|
420
|
+
frameworkReact
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
addCheck(
|
|
424
|
+
checks,
|
|
425
|
+
"dependencies.single-react",
|
|
426
|
+
sameReact
|
|
427
|
+
? "pass"
|
|
428
|
+
: "fail",
|
|
429
|
+
"Single React instance",
|
|
430
|
+
sameReact
|
|
431
|
+
? `BCP and the application resolve the same React package (${appReact.version}).`
|
|
432
|
+
: `BCP resolves React from ${frameworkReact.packageRoot}, but the application resolves React from ${appReact.packageRoot}. This can cause Invalid hook call errors.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
appReactDom &&
|
|
438
|
+
frameworkReactDom
|
|
439
|
+
) {
|
|
440
|
+
const sameRenderer =
|
|
441
|
+
samePackageRoot(
|
|
442
|
+
appReactDom,
|
|
443
|
+
frameworkReactDom
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
addCheck(
|
|
447
|
+
checks,
|
|
448
|
+
"dependencies.single-react-dom",
|
|
449
|
+
sameRenderer
|
|
450
|
+
? "pass"
|
|
451
|
+
: "fail",
|
|
452
|
+
"Single React DOM renderer",
|
|
453
|
+
sameRenderer
|
|
454
|
+
? `BCP and the application resolve the same React DOM package (${appReactDom.version}).`
|
|
455
|
+
: `BCP resolves react-dom from ${frameworkReactDom.packageRoot}, but the application resolves react-dom from ${appReactDom.packageRoot}. Avoid linked framework directories; test packed .tgz artifacts instead.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
const environment =
|
|
461
|
+
loadEnvironment(
|
|
462
|
+
rootDirectory,
|
|
463
|
+
"development"
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
addCheck(
|
|
467
|
+
checks,
|
|
468
|
+
"environment.development",
|
|
469
|
+
"pass",
|
|
470
|
+
"Development environment",
|
|
471
|
+
environment.files.length > 0
|
|
472
|
+
? `Loaded ${environment.files.join(", ")}. Public variables: ${Object.keys(environment.publicValues).length}.`
|
|
473
|
+
: "No development .env files were found; process environment and defaults remain available."
|
|
474
|
+
);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
addCheck(
|
|
477
|
+
checks,
|
|
478
|
+
"environment.development",
|
|
479
|
+
"fail",
|
|
480
|
+
"Development environment",
|
|
481
|
+
errorMessage(
|
|
482
|
+
error
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
const resolved =
|
|
489
|
+
await resolveBcpConfig(
|
|
490
|
+
rootDirectory
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
addCheck(
|
|
494
|
+
checks,
|
|
495
|
+
"config.resolved",
|
|
496
|
+
"pass",
|
|
497
|
+
"Framework config",
|
|
498
|
+
resolved.file
|
|
499
|
+
? `${path.basename(resolved.file)} is valid; server resolves to ${resolved.config.server.hostname}:${resolved.config.server.port}.`
|
|
500
|
+
: `No bcp.config file found; defaults/environment resolve server to ${resolved.config.server.hostname}:${resolved.config.server.port}.`
|
|
501
|
+
);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
addCheck(
|
|
504
|
+
checks,
|
|
505
|
+
"config.resolved",
|
|
506
|
+
"fail",
|
|
507
|
+
"Framework config",
|
|
508
|
+
errorMessage(
|
|
509
|
+
error
|
|
510
|
+
)
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (hasAppDirectory) {
|
|
515
|
+
try {
|
|
516
|
+
const pageRoutes =
|
|
517
|
+
scanRoutes(
|
|
518
|
+
appDirectory
|
|
519
|
+
);
|
|
520
|
+
const apiRoutes =
|
|
521
|
+
scanApiRoutes(
|
|
522
|
+
appDirectory
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
assertNoPageApiConflicts(
|
|
526
|
+
pageRoutes,
|
|
527
|
+
apiRoutes
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
addCheck(
|
|
531
|
+
checks,
|
|
532
|
+
"routes.discovery",
|
|
533
|
+
pageRoutes.length > 0 ||
|
|
534
|
+
apiRoutes.length > 0
|
|
535
|
+
? "pass"
|
|
536
|
+
: "warn",
|
|
537
|
+
"Route discovery",
|
|
538
|
+
`Discovered ${pageRoutes.length} page route(s) and ${apiRoutes.length} API route(s).`
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
validateClientBoundaries(
|
|
543
|
+
pageRoutes,
|
|
544
|
+
rootDirectory
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
addCheck(
|
|
548
|
+
checks,
|
|
549
|
+
"routes.client-boundaries",
|
|
550
|
+
"pass",
|
|
551
|
+
"Client boundaries",
|
|
552
|
+
"Hydrated routes do not expose detected server-only modules and client-hook directives are valid."
|
|
553
|
+
);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
addCheck(
|
|
556
|
+
checks,
|
|
557
|
+
"routes.client-boundaries",
|
|
558
|
+
"fail",
|
|
559
|
+
"Client boundaries",
|
|
560
|
+
errorMessage(
|
|
561
|
+
error
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
addCheck(
|
|
567
|
+
checks,
|
|
568
|
+
"routes.discovery",
|
|
569
|
+
"fail",
|
|
570
|
+
"Route discovery",
|
|
571
|
+
errorMessage(
|
|
572
|
+
error
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const summary = {
|
|
579
|
+
passed:
|
|
580
|
+
checks.filter(
|
|
581
|
+
(check) =>
|
|
582
|
+
check.status === "pass"
|
|
583
|
+
).length,
|
|
584
|
+
warnings:
|
|
585
|
+
checks.filter(
|
|
586
|
+
(check) =>
|
|
587
|
+
check.status === "warn"
|
|
588
|
+
).length,
|
|
589
|
+
failed:
|
|
590
|
+
checks.filter(
|
|
591
|
+
(check) =>
|
|
592
|
+
check.status === "fail"
|
|
593
|
+
).length,
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
frameworkVersion:
|
|
598
|
+
options.frameworkVersion,
|
|
599
|
+
rootDirectory,
|
|
600
|
+
runtime: {
|
|
601
|
+
node:
|
|
602
|
+
process.versions.node,
|
|
603
|
+
platform:
|
|
604
|
+
process.platform,
|
|
605
|
+
arch:
|
|
606
|
+
process.arch,
|
|
607
|
+
},
|
|
608
|
+
checks,
|
|
609
|
+
summary,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export async function collectInspectReport(
|
|
614
|
+
options: DeveloperToolOptions
|
|
615
|
+
): Promise<InspectReport> {
|
|
616
|
+
const rootDirectory =
|
|
617
|
+
path.resolve(
|
|
618
|
+
options.rootDirectory
|
|
619
|
+
);
|
|
620
|
+
const packageFile =
|
|
621
|
+
path.join(
|
|
622
|
+
rootDirectory,
|
|
623
|
+
"package.json"
|
|
624
|
+
);
|
|
625
|
+
const appDirectory =
|
|
626
|
+
path.join(
|
|
627
|
+
rootDirectory,
|
|
628
|
+
"app"
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
const environment =
|
|
632
|
+
loadEnvironment(
|
|
633
|
+
rootDirectory,
|
|
634
|
+
"development"
|
|
635
|
+
);
|
|
636
|
+
const resolved =
|
|
637
|
+
await resolveBcpConfig(
|
|
638
|
+
rootDirectory
|
|
639
|
+
);
|
|
640
|
+
const pageRoutes =
|
|
641
|
+
scanRoutes(
|
|
642
|
+
appDirectory
|
|
643
|
+
);
|
|
644
|
+
const apiRoutes =
|
|
645
|
+
scanApiRoutes(
|
|
646
|
+
appDirectory
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
assertNoPageApiConflicts(
|
|
650
|
+
pageRoutes,
|
|
651
|
+
apiRoutes
|
|
652
|
+
);
|
|
653
|
+
validateClientBoundaries(
|
|
654
|
+
pageRoutes,
|
|
655
|
+
rootDirectory
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
const bcp =
|
|
659
|
+
resolveInstalledPackage(
|
|
660
|
+
packageFile,
|
|
661
|
+
"bcp"
|
|
662
|
+
) ??
|
|
663
|
+
resolveInstalledPackage(
|
|
664
|
+
packageFile,
|
|
665
|
+
"@chidchanun/bcp"
|
|
666
|
+
);
|
|
667
|
+
const react =
|
|
668
|
+
resolveInstalledPackage(
|
|
669
|
+
packageFile,
|
|
670
|
+
"react"
|
|
671
|
+
);
|
|
672
|
+
const reactDom =
|
|
673
|
+
resolveInstalledPackage(
|
|
674
|
+
packageFile,
|
|
675
|
+
"react-dom"
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
frameworkVersion:
|
|
680
|
+
options.frameworkVersion,
|
|
681
|
+
rootDirectory,
|
|
682
|
+
runtime: {
|
|
683
|
+
node:
|
|
684
|
+
process.versions.node,
|
|
685
|
+
platform:
|
|
686
|
+
process.platform,
|
|
687
|
+
arch:
|
|
688
|
+
process.arch,
|
|
689
|
+
},
|
|
690
|
+
environment: {
|
|
691
|
+
mode:
|
|
692
|
+
"development",
|
|
693
|
+
files:
|
|
694
|
+
environment.files,
|
|
695
|
+
publicVariables:
|
|
696
|
+
Object.keys(
|
|
697
|
+
environment.publicValues
|
|
698
|
+
).sort(),
|
|
699
|
+
},
|
|
700
|
+
config: {
|
|
701
|
+
file:
|
|
702
|
+
resolved.file
|
|
703
|
+
? path.basename(
|
|
704
|
+
resolved.file
|
|
705
|
+
)
|
|
706
|
+
: null,
|
|
707
|
+
resolved:
|
|
708
|
+
resolved.config,
|
|
709
|
+
},
|
|
710
|
+
dependencies: {
|
|
711
|
+
bcp:
|
|
712
|
+
bcp?.version ??
|
|
713
|
+
null,
|
|
714
|
+
react:
|
|
715
|
+
react?.version ??
|
|
716
|
+
null,
|
|
717
|
+
reactDom:
|
|
718
|
+
reactDom?.version ??
|
|
719
|
+
null,
|
|
720
|
+
},
|
|
721
|
+
routes: {
|
|
722
|
+
pages:
|
|
723
|
+
pageRoutes.map(
|
|
724
|
+
(route) => ({
|
|
725
|
+
pathname:
|
|
726
|
+
route.pathname,
|
|
727
|
+
file:
|
|
728
|
+
relativeProjectPath(
|
|
729
|
+
rootDirectory,
|
|
730
|
+
route.filePath
|
|
731
|
+
),
|
|
732
|
+
})
|
|
733
|
+
),
|
|
734
|
+
api:
|
|
735
|
+
apiRoutes.map(
|
|
736
|
+
(route) => ({
|
|
737
|
+
pathname:
|
|
738
|
+
route.pathname,
|
|
739
|
+
file:
|
|
740
|
+
relativeProjectPath(
|
|
741
|
+
rootDirectory,
|
|
742
|
+
route.filePath
|
|
743
|
+
),
|
|
744
|
+
})
|
|
745
|
+
),
|
|
746
|
+
},
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export function compareVersions(
|
|
751
|
+
left: string,
|
|
752
|
+
right: string
|
|
753
|
+
): number {
|
|
754
|
+
const leftParts =
|
|
755
|
+
parseVersionParts(
|
|
756
|
+
left
|
|
757
|
+
);
|
|
758
|
+
const rightParts =
|
|
759
|
+
parseVersionParts(
|
|
760
|
+
right
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
for (
|
|
764
|
+
let index = 0;
|
|
765
|
+
index < 3;
|
|
766
|
+
index++
|
|
767
|
+
) {
|
|
768
|
+
if (
|
|
769
|
+
leftParts[index] >
|
|
770
|
+
rightParts[index]
|
|
771
|
+
) {
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (
|
|
776
|
+
leftParts[index] <
|
|
777
|
+
rightParts[index]
|
|
778
|
+
) {
|
|
779
|
+
return -1;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return 0;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function printDoctorReport(
|
|
787
|
+
report: DoctorReport
|
|
788
|
+
): void {
|
|
789
|
+
console.log("");
|
|
790
|
+
console.log(
|
|
791
|
+
`BCP Doctor v${report.frameworkVersion}`
|
|
792
|
+
);
|
|
793
|
+
console.log(
|
|
794
|
+
`Project: ${report.rootDirectory}`
|
|
795
|
+
);
|
|
796
|
+
console.log(
|
|
797
|
+
`Runtime: Node.js ${report.runtime.node} | ${report.runtime.platform} ${report.runtime.arch}`
|
|
798
|
+
);
|
|
799
|
+
console.log("");
|
|
800
|
+
|
|
801
|
+
for (const check of report.checks) {
|
|
802
|
+
const marker =
|
|
803
|
+
check.status === "pass"
|
|
804
|
+
? "PASS"
|
|
805
|
+
: check.status === "warn"
|
|
806
|
+
? "WARN"
|
|
807
|
+
: "FAIL";
|
|
808
|
+
|
|
809
|
+
console.log(
|
|
810
|
+
`[${marker}] ${check.label}: ${check.message}`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
console.log("");
|
|
815
|
+
console.log(
|
|
816
|
+
`Summary: ${report.summary.passed} passed, ${report.summary.warnings} warning(s), ${report.summary.failed} failed.`
|
|
817
|
+
);
|
|
818
|
+
console.log("");
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function printInspectReport(
|
|
822
|
+
report: InspectReport
|
|
823
|
+
): void {
|
|
824
|
+
console.log("");
|
|
825
|
+
console.log(
|
|
826
|
+
`BCP Inspect v${report.frameworkVersion}`
|
|
827
|
+
);
|
|
828
|
+
console.log(
|
|
829
|
+
`Project: ${report.rootDirectory}`
|
|
830
|
+
);
|
|
831
|
+
console.log(
|
|
832
|
+
`Runtime: Node.js ${report.runtime.node} | ${report.runtime.platform} ${report.runtime.arch}`
|
|
833
|
+
);
|
|
834
|
+
console.log("");
|
|
835
|
+
console.log(
|
|
836
|
+
"Environment:"
|
|
837
|
+
);
|
|
838
|
+
console.log(
|
|
839
|
+
` mode: ${report.environment.mode}`
|
|
840
|
+
);
|
|
841
|
+
console.log(
|
|
842
|
+
` files: ${report.environment.files.length > 0 ? report.environment.files.join(", ") : "(none)"}`
|
|
843
|
+
);
|
|
844
|
+
console.log(
|
|
845
|
+
` public variables: ${report.environment.publicVariables.length > 0 ? report.environment.publicVariables.join(", ") : "(none)"}`
|
|
846
|
+
);
|
|
847
|
+
console.log("");
|
|
848
|
+
console.log(
|
|
849
|
+
"Dependencies:"
|
|
850
|
+
);
|
|
851
|
+
console.log(
|
|
852
|
+
` bcp: ${report.dependencies.bcp ?? "not resolved"}`
|
|
853
|
+
);
|
|
854
|
+
console.log(
|
|
855
|
+
` react: ${report.dependencies.react ?? "not resolved"}`
|
|
856
|
+
);
|
|
857
|
+
console.log(
|
|
858
|
+
` react-dom: ${report.dependencies.reactDom ?? "not resolved"}`
|
|
859
|
+
);
|
|
860
|
+
console.log("");
|
|
861
|
+
console.log(
|
|
862
|
+
`Config: ${report.config.file ?? "defaults/environment"}`
|
|
863
|
+
);
|
|
864
|
+
console.log(
|
|
865
|
+
JSON.stringify(
|
|
866
|
+
report.config.resolved,
|
|
867
|
+
null,
|
|
868
|
+
2
|
|
869
|
+
)
|
|
870
|
+
);
|
|
871
|
+
console.log("");
|
|
872
|
+
console.log(
|
|
873
|
+
`Routes: ${report.routes.pages.length} page(s), ${report.routes.api.length} API route(s)`
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
for (const route of report.routes.pages) {
|
|
877
|
+
console.log(
|
|
878
|
+
` PAGE ${route.pathname} -> ${route.file}`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
for (const route of report.routes.api) {
|
|
883
|
+
console.log(
|
|
884
|
+
` API ${route.pathname} -> ${route.file}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
console.log("");
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function addInstalledPackageCheck(
|
|
892
|
+
checks: DoctorCheck[],
|
|
893
|
+
id: string,
|
|
894
|
+
label: string,
|
|
895
|
+
info: PackageManifestInfo | null
|
|
896
|
+
): void {
|
|
897
|
+
addCheck(
|
|
898
|
+
checks,
|
|
899
|
+
id,
|
|
900
|
+
info
|
|
901
|
+
? "pass"
|
|
902
|
+
: "fail",
|
|
903
|
+
label,
|
|
904
|
+
info
|
|
905
|
+
? `${info.version} at ${info.packageRoot}`
|
|
906
|
+
: "Package could not be resolved from the project."
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function addCheck(
|
|
911
|
+
checks: DoctorCheck[],
|
|
912
|
+
id: string,
|
|
913
|
+
status: DoctorStatus,
|
|
914
|
+
label: string,
|
|
915
|
+
message: string
|
|
916
|
+
): void {
|
|
917
|
+
checks.push({
|
|
918
|
+
id,
|
|
919
|
+
status,
|
|
920
|
+
label,
|
|
921
|
+
message,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function readProjectPackage(
|
|
926
|
+
packageFile: string
|
|
927
|
+
): ProjectPackageJson {
|
|
928
|
+
const value =
|
|
929
|
+
JSON.parse(
|
|
930
|
+
fs.readFileSync(
|
|
931
|
+
packageFile,
|
|
932
|
+
"utf8"
|
|
933
|
+
)
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
if (
|
|
937
|
+
!value ||
|
|
938
|
+
typeof value !== "object" ||
|
|
939
|
+
Array.isArray(
|
|
940
|
+
value
|
|
941
|
+
)
|
|
942
|
+
) {
|
|
943
|
+
throw new Error(
|
|
944
|
+
"package.json must contain a JSON object."
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return value as ProjectPackageJson;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function findDeclaredFrameworkDependency(
|
|
952
|
+
projectPackage: ProjectPackageJson
|
|
953
|
+
): {
|
|
954
|
+
name: string;
|
|
955
|
+
range: string;
|
|
956
|
+
} | null {
|
|
957
|
+
for (
|
|
958
|
+
const dependencies
|
|
959
|
+
of [
|
|
960
|
+
projectPackage.dependencies,
|
|
961
|
+
projectPackage.devDependencies,
|
|
962
|
+
]
|
|
963
|
+
) {
|
|
964
|
+
if (!dependencies) {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (
|
|
969
|
+
dependencies.bcp
|
|
970
|
+
) {
|
|
971
|
+
return {
|
|
972
|
+
name:
|
|
973
|
+
"bcp",
|
|
974
|
+
range:
|
|
975
|
+
dependencies.bcp,
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (
|
|
980
|
+
dependencies[
|
|
981
|
+
"@chidchanun/bcp"
|
|
982
|
+
]
|
|
983
|
+
) {
|
|
984
|
+
return {
|
|
985
|
+
name:
|
|
986
|
+
"@chidchanun/bcp",
|
|
987
|
+
range:
|
|
988
|
+
dependencies[
|
|
989
|
+
"@chidchanun/bcp"
|
|
990
|
+
],
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function resolveInstalledPackage(
|
|
999
|
+
projectPackageFile: string,
|
|
1000
|
+
packageName: string
|
|
1001
|
+
): PackageManifestInfo | null {
|
|
1002
|
+
if (
|
|
1003
|
+
!fs.existsSync(
|
|
1004
|
+
projectPackageFile
|
|
1005
|
+
)
|
|
1006
|
+
) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
try {
|
|
1011
|
+
const projectRequire =
|
|
1012
|
+
createRequire(
|
|
1013
|
+
projectPackageFile
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
return resolvePackageWithRequire(
|
|
1017
|
+
projectRequire,
|
|
1018
|
+
packageName
|
|
1019
|
+
);
|
|
1020
|
+
} catch {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function resolveFrameworkPackage(
|
|
1026
|
+
packageName: string
|
|
1027
|
+
): PackageManifestInfo | null {
|
|
1028
|
+
try {
|
|
1029
|
+
return resolvePackageWithRequire(
|
|
1030
|
+
frameworkRequire,
|
|
1031
|
+
packageName
|
|
1032
|
+
);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function resolvePackageWithRequire(
|
|
1039
|
+
resolver: NodeJS.Require,
|
|
1040
|
+
packageName: string
|
|
1041
|
+
): PackageManifestInfo {
|
|
1042
|
+
let manifestPath:
|
|
1043
|
+
string;
|
|
1044
|
+
|
|
1045
|
+
try {
|
|
1046
|
+
manifestPath =
|
|
1047
|
+
resolver.resolve(
|
|
1048
|
+
`${packageName}/package.json`
|
|
1049
|
+
);
|
|
1050
|
+
} catch {
|
|
1051
|
+
const entry =
|
|
1052
|
+
resolver.resolve(
|
|
1053
|
+
packageName
|
|
1054
|
+
);
|
|
1055
|
+
const discovered =
|
|
1056
|
+
findNearestPackageManifest(
|
|
1057
|
+
path.dirname(
|
|
1058
|
+
entry
|
|
1059
|
+
)
|
|
1060
|
+
);
|
|
1061
|
+
|
|
1062
|
+
if (!discovered) {
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
`Could not locate package.json for ${packageName}.`
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
manifestPath =
|
|
1069
|
+
discovered;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const manifest =
|
|
1073
|
+
JSON.parse(
|
|
1074
|
+
fs.readFileSync(
|
|
1075
|
+
manifestPath,
|
|
1076
|
+
"utf8"
|
|
1077
|
+
)
|
|
1078
|
+
) as {
|
|
1079
|
+
version?: unknown;
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
if (
|
|
1083
|
+
typeof manifest.version !==
|
|
1084
|
+
"string"
|
|
1085
|
+
) {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
`${packageName} package.json does not contain a version.`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const packageRoot =
|
|
1092
|
+
realPath(
|
|
1093
|
+
path.dirname(
|
|
1094
|
+
manifestPath
|
|
1095
|
+
)
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
return {
|
|
1099
|
+
version:
|
|
1100
|
+
manifest.version,
|
|
1101
|
+
manifestPath:
|
|
1102
|
+
realPath(
|
|
1103
|
+
manifestPath
|
|
1104
|
+
),
|
|
1105
|
+
packageRoot,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function findNearestPackageManifest(
|
|
1110
|
+
startDirectory: string
|
|
1111
|
+
): string | null {
|
|
1112
|
+
let current =
|
|
1113
|
+
path.resolve(
|
|
1114
|
+
startDirectory
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
while (true) {
|
|
1118
|
+
const candidate =
|
|
1119
|
+
path.join(
|
|
1120
|
+
current,
|
|
1121
|
+
"package.json"
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
if (
|
|
1125
|
+
fs.existsSync(
|
|
1126
|
+
candidate
|
|
1127
|
+
)
|
|
1128
|
+
) {
|
|
1129
|
+
return candidate;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const parent =
|
|
1133
|
+
path.dirname(
|
|
1134
|
+
current
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
if (parent === current) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
current =
|
|
1142
|
+
parent;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function samePackageRoot(
|
|
1147
|
+
left: PackageManifestInfo,
|
|
1148
|
+
right: PackageManifestInfo
|
|
1149
|
+
): boolean {
|
|
1150
|
+
const normalize =
|
|
1151
|
+
(value: string) =>
|
|
1152
|
+
process.platform === "win32"
|
|
1153
|
+
? value.toLowerCase()
|
|
1154
|
+
: value;
|
|
1155
|
+
|
|
1156
|
+
return normalize(
|
|
1157
|
+
left.packageRoot
|
|
1158
|
+
) === normalize(
|
|
1159
|
+
right.packageRoot
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function realPath(
|
|
1164
|
+
value: string
|
|
1165
|
+
): string {
|
|
1166
|
+
try {
|
|
1167
|
+
return fs.realpathSync(
|
|
1168
|
+
value
|
|
1169
|
+
);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return path.resolve(
|
|
1172
|
+
value
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function isDirectory(
|
|
1178
|
+
directory: string
|
|
1179
|
+
): boolean {
|
|
1180
|
+
try {
|
|
1181
|
+
return fs.statSync(
|
|
1182
|
+
directory
|
|
1183
|
+
).isDirectory();
|
|
1184
|
+
} catch {
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function relativeProjectPath(
|
|
1190
|
+
rootDirectory: string,
|
|
1191
|
+
filePath: string
|
|
1192
|
+
): string {
|
|
1193
|
+
return path.relative(
|
|
1194
|
+
rootDirectory,
|
|
1195
|
+
filePath
|
|
1196
|
+
).replace(
|
|
1197
|
+
/\\/g,
|
|
1198
|
+
"/"
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function parseVersionParts(
|
|
1203
|
+
version: string
|
|
1204
|
+
): [number, number, number] {
|
|
1205
|
+
const match =
|
|
1206
|
+
/^(\d+)\.(\d+)\.(\d+)/.exec(
|
|
1207
|
+
version
|
|
1208
|
+
);
|
|
1209
|
+
|
|
1210
|
+
if (!match) {
|
|
1211
|
+
return [
|
|
1212
|
+
0,
|
|
1213
|
+
0,
|
|
1214
|
+
0,
|
|
1215
|
+
];
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
return [
|
|
1219
|
+
Number(
|
|
1220
|
+
match[1]
|
|
1221
|
+
),
|
|
1222
|
+
Number(
|
|
1223
|
+
match[2]
|
|
1224
|
+
),
|
|
1225
|
+
Number(
|
|
1226
|
+
match[3]
|
|
1227
|
+
),
|
|
1228
|
+
];
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function errorMessage(
|
|
1232
|
+
error: unknown
|
|
1233
|
+
): string {
|
|
1234
|
+
return error instanceof Error
|
|
1235
|
+
? error.message
|
|
1236
|
+
: String(
|
|
1237
|
+
error
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
class DeveloperToolJsonError extends Error {
|
|
1242
|
+
constructor() {
|
|
1243
|
+
super(
|
|
1244
|
+
"BCP inspect failed."
|
|
1245
|
+
);
|
|
1246
|
+
this.name =
|
|
1247
|
+
"DeveloperToolJsonError";
|
|
1248
|
+
}
|
|
1249
|
+
}
|