Skip to content

Commit a8f14ef

Browse files
authored
Merge pull request #1298 from joshunrau/serve-instrument
improve error logging in `serve-instrument` and add `--verbose` option
2 parents 12224be + ef8b289 commit a8f14ef

File tree

5 files changed

+84
-42
lines changed

5 files changed

+84
-42
lines changed

packages/serve-instrument/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"esbuild": "catalog:"
2121
},
2222
"devDependencies": {
23+
"@douglasneuroinformatics/libjs": "catalog:",
2324
"@douglasneuroinformatics/libui": "catalog:",
2425
"@opendatacapture/instrument-bundler": "workspace:",
2526
"@opendatacapture/react-core": "workspace:*",

packages/serve-instrument/src/cli.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ program
3333
.allowExcessArguments(false)
3434
.argument('<target>', 'the directory containing the instrument', parseTarget)
3535
.option('-p --port <number>', 'the port to run the dev server on', parsePort, 3000)
36+
.option('-v, --verbose', 'enable verbose logging (includes request logs and build timing)')
3637
.action(async (target: string) => {
37-
const { port } = program.opts<{ port: number }>();
38-
const server = await Server.create({ port, target });
38+
const { port, verbose } = program.opts<{ port: number; verbose: boolean }>();
39+
const server = await Server.create({ port, target, verbose: verbose ?? false });
3940
await server.start();
4041
});
4142

packages/serve-instrument/src/server.tsx

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@ import * as fs from 'node:fs';
66
import * as http from 'node:http';
77
import * as path from 'node:path';
88

9-
import {
10-
bundle,
11-
BUNDLER_FILE_EXT_REGEX,
12-
inferLoader,
13-
InstrumentBundlerError
14-
} from '@opendatacapture/instrument-bundler';
9+
import { formatError } from '@douglasneuroinformatics/libjs';
10+
import { bundle, BUNDLER_FILE_EXT_REGEX, inferLoader } from '@opendatacapture/instrument-bundler';
1511
import type { BundlerInput } from '@opendatacapture/instrument-bundler';
1612
import { encodeUnicodeToBase64 } from '@opendatacapture/runtime-internal';
1713
import { generateMetadata, resolveRuntimeAsset } from '@opendatacapture/runtime-meta';
@@ -24,13 +20,28 @@ import type { RootProps } from './root';
2420

2521
declare const __TAILWIND_STYLES__: string;
2622

23+
function timestamp(): string {
24+
return chalk.dim(new Date().toLocaleTimeString('en', { hour12: false }));
25+
}
26+
27+
function log(message: string): void {
28+
console.log(`${timestamp()} ${message}`);
29+
}
30+
31+
function logError(message: string): void {
32+
console.error(`${timestamp()} ${message}`);
33+
}
34+
2735
class InstrumentLoader {
2836
private encodedBundle: null | string;
2937

30-
constructor(private readonly target: string) {
38+
constructor(
39+
private readonly target: string,
40+
private readonly verbose: boolean
41+
) {
3142
this.encodedBundle = null;
3243
fs.watch(target, { recursive: false }, () => {
33-
console.log(chalk.yellow('↺') + chalk.dim(' File changed, rebuilding instrument...'));
44+
log(chalk.yellow('↺') + chalk.dim(' File changed, rebuilding...'));
3445
void this.updateEncodedBundle();
3546
});
3647
}
@@ -58,35 +69,36 @@ class InstrumentLoader {
5869
name: path.basename(filepath)
5970
});
6071
}
72+
const start = Date.now();
6173
try {
6274
this.encodedBundle = encodeUnicodeToBase64(await bundle({ inputs }));
63-
console.log(chalk.green('✓') + chalk.dim(' Bundle ready'));
75+
const elapsed = Date.now() - start;
76+
const timing = this.verbose ? chalk.dim(` (${elapsed}ms)`) : '';
77+
log(chalk.green('✓') + chalk.dim(' Bundle ready') + timing);
6478
} catch (err) {
65-
if (!(err instanceof InstrumentBundlerError)) {
66-
console.error(err);
67-
}
68-
console.error(chalk.red('✘ Failed to compile instrument'));
79+
const formattedError = err instanceof Error ? formatError(err) : err;
80+
logError(chalk.dim(String(formattedError)) + '\n');
81+
logError(chalk.red('✘') + chalk.bold.red(' Error: Failed to compile instrument'));
6982
}
7083
}
7184
}
7285

7386
class RequestHandler {
7487
private instrumentLoader: InstrumentLoader;
7588
private runtimeMetadata: RuntimeMetadataMap;
89+
private verbose: boolean;
7690

77-
constructor(params: { instrumentLoader: InstrumentLoader; runtimeMetadata: RuntimeMetadataMap }) {
91+
constructor(params: { instrumentLoader: InstrumentLoader; runtimeMetadata: RuntimeMetadataMap; verbose: boolean }) {
7892
this.instrumentLoader = params.instrumentLoader;
7993
this.runtimeMetadata = params.runtimeMetadata;
94+
this.verbose = params.verbose;
8095
}
8196

8297
async handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
8398
if (req.method !== 'GET') {
8499
res.writeHead(405, { 'Content-Type': 'text/plain' });
85100
res.end('Method Not Allowed');
86-
return;
87-
}
88-
89-
if (req.url === '/') {
101+
} else if (req.url === '/') {
90102
const encodedBundle = await this.instrumentLoader.getEncodedBundle();
91103
if (encodedBundle) {
92104
res.writeHead(200, { 'Content-Type': 'text/html' });
@@ -145,11 +157,12 @@ export class Server {
145157
this.server = http.createServer((...args) => void this.handler.handle(...args));
146158
}
147159

148-
static async create({ port, target }: { port: number; target: string }) {
160+
static async create({ port, target, verbose }: { port: number; target: string; verbose: boolean }) {
149161
return new this({
150162
handler: new RequestHandler({
151-
instrumentLoader: new InstrumentLoader(target),
152-
runtimeMetadata: await generateMetadata({ rootDir: import.meta.dirname })
163+
instrumentLoader: new InstrumentLoader(target, verbose),
164+
runtimeMetadata: await generateMetadata({ rootDir: import.meta.dirname }),
165+
verbose
153166
}),
154167
port
155168
});
@@ -158,7 +171,14 @@ export class Server {
158171
async start(): Promise<void> {
159172
return new Promise((resolve) => {
160173
this.server.listen(this.port, () => {
161-
console.log(chalk.green('✓') + ' Listening on ' + chalk.cyan.underline(`http://localhost:${this.port}`));
174+
log(
175+
chalk.green('✓') +
176+
chalk.bold(' Ready') +
177+
' ' +
178+
chalk.dim('➜') +
179+
' ' +
180+
chalk.cyan.underline(`http://localhost:${this.port}`)
181+
);
162182
resolve();
163183
});
164184
});

pnpm-lock.yaml

Lines changed: 37 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ catalog:
44
'@casl/prisma': '^1.4.1'
55
'@douglasneuroinformatics/esbuild-plugin-prisma': 'latest'
66
'@douglasneuroinformatics/libcrypto': '^0.0.4'
7-
'@douglasneuroinformatics/libjs': ^3.1.1
7+
'@douglasneuroinformatics/libjs': ^3.2.0
88
'@douglasneuroinformatics/libpasswd': 'latest'
99
'@douglasneuroinformatics/libstats': 'latest'
1010
'@douglasneuroinformatics/libui': ^6.1.2

0 commit comments

Comments
 (0)