Skip to content

Commit 7b54e0a

Browse files
authored
Merge pull request #19 from DouglasNeuroInformatics/dev
chore: add onComplete callback to options
2 parents c87df97 + b176655 commit 7b54e0a

File tree

4 files changed

+58
-8
lines changed

4 files changed

+58
-8
lines changed

src/meta/__tests__/build.test.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ const { loadUserConfig, parseEntryFromFunction } = vi.hoisted(() => ({
1515
parseEntryFromFunction: vi.fn()
1616
}));
1717

18+
const esbuildMock = {
19+
build: vi.fn()
20+
};
21+
1822
vi.mock('../load.js', () => ({ loadUserConfig }));
1923
vi.mock('../parse.js', () => ({ parseEntryFromFunction }));
2024

2125
describe('bundle', () => {
22-
const esbuild = {
23-
build: vi.fn()
24-
};
25-
2626
beforeAll(() => {
27-
vi.doMock('esbuild', () => esbuild);
27+
vi.doMock('esbuild', () => esbuildMock);
2828
});
2929

3030
afterAll(() => {
@@ -33,7 +33,7 @@ describe('bundle', () => {
3333

3434
it('should return an error if esbuild throws', async () => {
3535
const cause = new Error('Something went wrong');
36-
esbuild.build.mockImplementationOnce(() => {
36+
esbuildMock.build.mockImplementationOnce(() => {
3737
throw cause;
3838
});
3939
await expect(
@@ -47,7 +47,7 @@ describe('bundle', () => {
4747
});
4848

4949
it('should an ok result on success', async () => {
50-
esbuild.build.mockResolvedValueOnce({});
50+
esbuildMock.build.mockResolvedValueOnce({});
5151
const result = await bundle({
5252
config: { build: { outfile: 'out.js' } },
5353
entrySpecifier: './app.ts',
@@ -98,10 +98,12 @@ describe('buildProd', () => {
9898

9999
it('should correctly bundle the example application as a module', { timeout: 10000 }, async () => {
100100
const outfile = path.join(outdir, 'module.js');
101+
const onComplete = vi.fn();
101102
loadUserConfig.mockReturnValue(
102103
okAsync({
103104
build: {
104105
mode: 'module',
106+
onComplete,
105107
outfile
106108
},
107109
entry: vi.fn(),
@@ -118,5 +120,35 @@ describe('buildProd', () => {
118120
const appContainer = await import(outfile).then((module) => module.default as AppContainer);
119121
const app = appContainer.getApplicationInstance();
120122
expect(app).toBeDefined();
123+
expect(onComplete).toHaveBeenCalledOnce();
124+
});
125+
126+
it('should handle errors in the onComplete callback', async () => {
127+
vi.doMock('esbuild', () => esbuildMock);
128+
const callbackError = new Error('Something went wrong');
129+
const onComplete = vi.fn().mockImplementation(() => {
130+
throw callbackError;
131+
});
132+
loadUserConfig.mockReturnValue(
133+
okAsync({
134+
build: {
135+
mode: 'module',
136+
onComplete,
137+
outfile: '/dev/null'
138+
},
139+
entry: vi.fn()
140+
} satisfies UserConfigOptions)
141+
);
142+
parseEntryFromFunction.mockReturnValueOnce(ok('./example/app.js'));
143+
const result = await buildProd({ configFile });
144+
expect(result.isErr()).toBe(true);
145+
expect(result).toMatchObject({
146+
error: {
147+
cause: callbackError,
148+
message: 'An error occurred in the user-specified `onComplete` callback'
149+
}
150+
});
151+
expect(onComplete).toHaveBeenCalledOnce();
152+
vi.doUnmock('esbuild');
121153
});
122154
});

src/meta/build.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,26 @@ export const bundle = fromAsyncThrowable(
6565
export function buildProd({ configFile }: { configFile: string }): ResultAsync<void, typeof RuntimeException.Instance> {
6666
return loadUserConfig(configFile).andThen((config) => {
6767
return parseEntryFromFunction(config.entry).asyncAndThen((entrySpecifier) => {
68-
return bundle({
68+
const result = bundle({
6969
config,
7070
entrySpecifier,
7171
resolveDir: path.dirname(configFile)
7272
});
73+
const { onComplete } = config.build;
74+
if (onComplete) {
75+
const callback = fromAsyncThrowable(
76+
async () => {
77+
await onComplete();
78+
},
79+
(err) => {
80+
return new RuntimeException('An error occurred in the user-specified `onComplete` callback', {
81+
cause: err
82+
});
83+
}
84+
);
85+
return result.andThen(callback);
86+
}
87+
return result;
7388
});
7489
});
7590
}

src/meta/load.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const $UserConfigOptions: z.ZodType<UserConfigOptions> = z.object({
1818
build: z.object({
1919
esbuildOptions: z.record(z.any()).optional(),
2020
mode: z.enum(['module', 'server']).optional(),
21+
onComplete: z.function().returns(z.any()).optional(),
2122
outfile: z.string().min(1)
2223
}),
2324
entry: $EntryFunction,

src/user-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export type UserConfigOptions = {
1919
* @default 'server'
2020
*/
2121
mode?: 'module' | 'server';
22+
/** A callback function to invoke when the build is complete */
23+
onComplete?: () => Promisable<void>;
2224
/** The path where the bundle should be written */
2325
outfile: string;
2426
};

0 commit comments

Comments
 (0)