Skip to content

Commit eb9c24c

Browse files
committed
cli: improve test coverage and prefer logger over console
1 parent 3c0380b commit eb9c24c

File tree

8 files changed

+296
-80
lines changed

8 files changed

+296
-80
lines changed

packages/cli/src/app.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
import { Logger } from '@ocap/utils';
12
import path from 'node:path';
23
import yargs from 'yargs';
34
import { hideBin } from 'yargs/helpers';
45

5-
import { createBundle } from './commands/bundle.ts';
6+
import { bundleSource } from './commands/bundle.ts';
67
import { getServer } from './commands/serve.ts';
78
import { watchDir } from './commands/watch.ts';
89
import { defaultConfig } from './config.ts';
910
import type { Config } from './config.ts';
1011
import { withTimeout } from './utils.ts';
1112

13+
const logger = new Logger('cli');
14+
1215
await yargs(hideBin(process.argv))
1316
.usage('$0 <command> [options]')
1417
.demandCommand(1)
@@ -26,7 +29,9 @@ await yargs(hideBin(process.argv))
2629
describe: 'The files or directories of files to bundle',
2730
}),
2831
async (args) => {
29-
await Promise.all(args.targets.map(createBundle));
32+
await Promise.all(
33+
args.targets.map(async (target) => bundleSource(logger, target)),
34+
);
3035
},
3136
)
3237
.command(
@@ -55,7 +60,7 @@ await yargs(hideBin(process.argv))
5560
},
5661
dir: resolvedDir,
5762
};
58-
console.info(`starting ${appName} in ${resolvedDir} on ${url}`);
63+
logger.info(`starting ${appName} in ${resolvedDir} on ${url}`);
5964
const server = getServer(config);
6065
await server.listen();
6166
},
@@ -71,19 +76,19 @@ await yargs(hideBin(process.argv))
7176
describe: 'The directory to watch',
7277
}),
7378
(args) => {
74-
const { ready, error } = watchDir(args.dir);
79+
const { ready, error } = watchDir(args.dir, logger);
7580
let handleClose: undefined | (() => Promise<void>);
7681

7782
ready
7883
.then((close) => {
7984
handleClose = close;
80-
console.info(`Watching ${args.dir}...`);
85+
logger.info(`Watching ${args.dir}...`);
8186
return undefined;
8287
})
83-
.catch(console.error);
88+
.catch(logger.error);
8489

8590
error.catch(async (reason) => {
86-
console.error(reason);
91+
logger.error(reason);
8792
// If watching started, close the watcher.
8893
return handleClose ? withTimeout(handleClose(), 400) : undefined;
8994
});
@@ -109,18 +114,21 @@ await yargs(hideBin(process.argv))
109114
const closeHandlers: (() => Promise<void>)[] = [];
110115
const resolvedDir = path.resolve(args.dir);
111116

112-
await createBundle(resolvedDir);
117+
await bundleSource(logger, resolvedDir);
113118

114119
const handleClose = async (): Promise<void> => {
115120
await Promise.all(
116121
closeHandlers.map(async (close) => withTimeout(close(), 400)),
117122
);
118123
};
119124

120-
const { ready: watchReady, error: watchError } = watchDir(resolvedDir);
125+
const { ready: watchReady, error: watchError } = watchDir(
126+
resolvedDir,
127+
logger,
128+
);
121129

122130
watchError.catch(async (reason) => {
123-
console.error(reason);
131+
logger.error(reason);
124132
await handleClose();
125133
});
126134

@@ -136,7 +144,7 @@ await yargs(hideBin(process.argv))
136144
const { close: closeServer, port } = await server.listen();
137145
closeHandlers.push(closeServer);
138146

139-
console.info(`bundling and serving ${resolvedDir} on localhost:${port}`);
147+
logger.info(`bundling and serving ${resolvedDir} on localhost:${port}`);
140148
},
141149
)
142150
.help('help')
Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
1+
import { Logger } from '@ocap/utils';
12
import { readFile, rm } from 'fs/promises';
23
import { basename } from 'path';
34
import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest';
45

5-
import { createBundleFile, createBundleDir } from './bundle.ts';
6+
import { bundleFile, bundleDir, bundleSource } from './bundle.ts';
67
import {
78
makeTestBundleStage,
89
validTestBundleNames,
910
} from '../../test/bundles.ts';
1011
import { fileExists } from '../file.ts';
1112

12-
const mocks = vi.hoisted(() => ({
13-
bundleSource: vi.fn(),
14-
}));
13+
const mocks = vi.hoisted(() => {
14+
return {
15+
endoBundleSource: vi.fn(),
16+
Logger: vi.fn(() => ({
17+
info: vi.fn(),
18+
error: vi.fn(),
19+
subLogger: vi.fn(),
20+
})),
21+
isDirectory: vi.fn(),
22+
};
23+
});
1524

1625
vi.mock('@endo/bundle-source', () => ({
17-
default: mocks.bundleSource,
26+
default: mocks.endoBundleSource,
1827
}));
1928

2029
vi.mock('@endo/init', () => ({}));
2130

31+
vi.mock('@ocap/utils', () => ({
32+
Logger: mocks.Logger,
33+
}));
34+
35+
vi.mock('../file.ts', async (importOriginal) => ({
36+
...(await importOriginal()),
37+
isDirectory: mocks.isDirectory,
38+
}));
39+
2240
describe('bundle', async () => {
41+
let logger: Logger;
42+
2343
const { testBundleRoot, getTestBundleSpecs, globBundles, resolveBundlePath } =
2444
await makeTestBundleStage();
2545
const testBundleSpecs = getTestBundleSpecs(validTestBundleNames);
@@ -32,20 +52,22 @@ describe('bundle', async () => {
3252
afterAll(deleteTestBundles);
3353

3454
beforeEach(async () => {
35-
vi.resetModules();
3655
await deleteTestBundles();
56+
vi.resetModules();
57+
logger = new Logger();
58+
vi.resetAllMocks();
3759
});
3860

39-
describe('createBundleFile', () => {
61+
describe('bundleFile', () => {
4062
it.each(testBundleSpecs)(
4163
'bundles a single file: $name',
4264
async ({ source, bundle }) => {
4365
expect(await fileExists(bundle)).toBe(false);
4466

4567
const testContent = { source: 'test-content' };
46-
mocks.bundleSource.mockImplementationOnce(() => testContent);
68+
mocks.endoBundleSource.mockImplementationOnce(() => testContent);
4769

48-
await createBundleFile(source);
70+
await bundleFile(logger, source);
4971

5072
expect(await fileExists(bundle)).toBe(true);
5173

@@ -57,27 +79,27 @@ describe('bundle', async () => {
5779
},
5880
);
5981

60-
it('calls console.error if bundling fails', async () => {
61-
const consoleErrorSpy = vi.spyOn(console, 'error');
82+
it('calls logger.error if bundling fails', async () => {
83+
const loggerErrorSpy = vi.spyOn(logger, 'error');
6284
const badBundle = resolveBundlePath('bad-vat.fails');
63-
await createBundleFile(badBundle);
64-
expect(consoleErrorSpy).toHaveBeenCalledOnce();
85+
await bundleFile(logger, badBundle);
86+
expect(loggerErrorSpy).toHaveBeenCalledOnce();
6587
});
6688
});
6789

68-
describe('createBundleDir', () => {
90+
describe('bundleDir', () => {
6991
it('bundles a directory', async () => {
7092
expect(await globBundles()).toStrictEqual([]);
7193

7294
// mocked bundleSource fails iff the target filename has '.fails.'
73-
mocks.bundleSource.mockImplementation((bundlePath) => {
95+
mocks.endoBundleSource.mockImplementation((bundlePath) => {
7496
if (bundlePath.includes('.fails.')) {
7597
throw new Error(`Failed to bundle ${bundlePath}`);
7698
}
7799
return 'test content';
78100
});
79101

80-
await createBundleDir(testBundleRoot);
102+
await bundleDir(logger, testBundleRoot);
81103

82104
const bundledOutputs = (await globBundles()).map((bundlePath) =>
83105
basename(bundlePath, '.bundle'),
@@ -88,4 +110,18 @@ describe('bundle', async () => {
88110
expect(bundledOutputs).toStrictEqual(validTestBundleNames);
89111
});
90112
});
113+
114+
describe('bundleSource', () => {
115+
it('calls logger.error if bundling fails', async () => {
116+
mocks.isDirectory.mockImplementationOnce(() => {
117+
throw new Error('test error');
118+
});
119+
const loggerErrorSpy = vi.spyOn(logger, 'error');
120+
await bundleSource(logger, resolveBundlePath('test'));
121+
expect(loggerErrorSpy).toHaveBeenCalledWith(
122+
expect.stringContaining('error bundling target'),
123+
expect.any(Error),
124+
);
125+
});
126+
});
91127
});
Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import '@endo/init';
2-
import bundleSource from '@endo/bundle-source';
2+
import endoBundleSource from '@endo/bundle-source';
3+
import { Logger } from '@ocap/utils';
34
import { glob } from 'glob';
45
import { writeFile } from 'node:fs/promises';
56
import { resolve, join } from 'node:path';
@@ -10,50 +11,63 @@ import { resolveBundlePath } from '../path.ts';
1011
/**
1112
* Create a bundle given path to an entry point.
1213
*
14+
* @param logger - The logger to use for logging.
1315
* @param sourcePath - Path to the source file that is the root of the bundle.
14-
* @param destinationPath - Optional path to which to write the bundle.
16+
* @param targetPath - Optional path to which to write the bundle.
1517
* If not provided, defaults to sourcePath with `.bundle` extension.
1618
* @returns A promise that resolves when the bundle has been written.
1719
*/
18-
export async function createBundleFile(
20+
export async function bundleFile(
21+
logger: Logger,
1922
sourcePath: string,
20-
destinationPath?: string,
23+
targetPath?: string,
2124
): Promise<void> {
2225
const sourceFullPath = resolve(sourcePath);
23-
const bundlePath = destinationPath ?? resolveBundlePath(sourceFullPath);
26+
const bundlePath = targetPath ?? resolveBundlePath(sourceFullPath);
2427
try {
25-
const bundle = await bundleSource(sourceFullPath);
26-
const bundleString = JSON.stringify(bundle);
27-
await writeFile(bundlePath, bundleString);
28-
console.log(`wrote ${bundlePath}: ${new Blob([bundleString]).size} bytes`);
28+
const bundle = await endoBundleSource(sourceFullPath);
29+
const bundleContent = JSON.stringify(bundle);
30+
await writeFile(bundlePath, bundleContent);
31+
logger.info(`wrote ${bundlePath}: ${new Blob([bundleContent]).size} bytes`);
2932
} catch (problem) {
30-
console.error(problem);
33+
logger.error(`error bundling file ${sourceFullPath}`, problem);
3134
}
3235
}
3336

3437
/**
3538
* Create a bundle given path to an entry point.
3639
*
40+
* @param logger - The logger to use for logging.
3741
* @param sourceDir - Path to a directory of source files to bundle.
3842
* @returns A promise that resolves when the bundles have been written.
3943
*/
40-
export async function createBundleDir(sourceDir: string): Promise<void> {
41-
console.log('bundling dir', sourceDir);
44+
export async function bundleDir(
45+
logger: Logger,
46+
sourceDir: string,
47+
): Promise<void> {
48+
logger.info('bundling dir', sourceDir);
4249
await Promise.all(
4350
(await glob(join(sourceDir, '*.js'))).map(
44-
async (source) => await createBundleFile(source),
51+
async (source) => await bundleFile(logger, source),
4552
),
4653
);
4754
}
4855

4956
/**
5057
* Bundle a target file or every file in the target directory.
5158
*
52-
* @param target The file or directory to apply the bundler to.
59+
* @param logger - The logger to use for logging.
60+
* @param target - The file or directory to apply the bundler to.
5361
* @returns A promise that resolves when bundling is done.
5462
*/
55-
export async function createBundle(target: string): Promise<void> {
56-
await ((await isDirectory(target)) ? createBundleDir : createBundleFile)(
57-
target,
58-
);
63+
export async function bundleSource(
64+
logger: Logger,
65+
target: string,
66+
): Promise<void> {
67+
try {
68+
const targetIsDirectory = await isDirectory(target);
69+
await (targetIsDirectory ? bundleDir : bundleFile)(logger, target);
70+
} catch (problem) {
71+
logger.error(`error bundling target ${target}`, problem);
72+
}
5973
}

0 commit comments

Comments
 (0)