/
hookActions.ts
51 lines (47 loc) · 1.75 KB
/
hookActions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { EventEmitter } from 'node:events';
import process from 'node:process';
import { HookAction, PluginDriver } from './PluginDriver';
function formatAction([pluginName, hookName, args]: HookAction): string {
const action = `(${pluginName}) ${hookName}`;
const s = JSON.stringify;
switch (hookName) {
case 'resolveId':
return `${action} ${s(args[0])} ${s(args[1])}`;
case 'load':
return `${action} ${s(args[0])}`;
case 'transform':
return `${action} ${s(args[1])}`;
case 'shouldTransformCachedModule':
return `${action} ${s((args[0] as { id: string }).id)}`;
case 'moduleParsed':
return `${action} ${s((args[0] as { id: string }).id)}`;
}
return action;
}
// We do not directly listen on process to avoid max listeners warnings for
// complicated build processes
const beforeExitEvent = 'beforeExit';
const beforeExitEmitter = new EventEmitter();
beforeExitEmitter.setMaxListeners(0);
process.on(beforeExitEvent, () => beforeExitEmitter.emit(beforeExitEvent));
export async function catchUnfinishedHookActions<T>(
pluginDriver: PluginDriver,
callback: () => Promise<T>
): Promise<T> {
let handleEmptyEventLoop: () => void;
const emptyEventLoopPromise = new Promise<T>((_, reject) => {
handleEmptyEventLoop = () => {
const unfulfilledActions = pluginDriver.getUnfulfilledHookActions();
reject(
new Error(
`Unexpected early exit. This happens when Promises returned by plugins cannot resolve. Unfinished hook action(s) on exit:\n` +
[...unfulfilledActions].map(formatAction).join('\n')
)
);
};
beforeExitEmitter.once(beforeExitEvent, handleEmptyEventLoop);
});
const result = await Promise.race([callback(), emptyEventLoopPromise]);
beforeExitEmitter.off(beforeExitEvent, handleEmptyEventLoop!);
return result;
}