1import { inspect } from 'node:util';
2import { writeSync } from 'node:fs';
3
4export const printf = (...args: unknown[]) => {
5 writeSync(
6 process.stderr.fd,
7 args
8 .map(arg => {
9 return typeof arg === 'object' && arg ? inspect(arg) : `${arg}`;
10 })
11 .join(' ') + '\n'
12 );
13};
14
15const MAX_STACK_DEPTH = 10;
16
17const isNodeSite = (site: NodeJS.CallSite) =>
18 !!site.getFileName()?.startsWith('node:');
19const isNodeInternalSite = (site: NodeJS.CallSite) =>
20 !!site.getFileName()?.startsWith('node:internal/');
21
22export class StackFrame {
23 isToplevel: boolean;
24 isNative: boolean;
25 isConstructor: boolean;
26 typeName: string | null;
27 functionName: string | null;
28 methodName: string | null;
29 fileName: string | null;
30 lineNumber: number | null;
31 columnNumber: number | null;
32 constructor(stack: NodeJS.CallSite[], offset: number) {
33 let idx = offset;
34 let site: NodeJS.CallSite | undefined = stack[idx];
35 while (idx < stack.length && isNodeSite((site = stack[idx]))) idx++;
36 this.fileName = site.getFileName() || null;
37 this.lineNumber = site.getLineNumber();
38 this.columnNumber = site.getColumnNumber();
39 this.functionName = site.getFunctionName();
40 do {
41 this.isToplevel = site.isToplevel();
42 this.isNative = site.isNative();
43 this.isConstructor = site.isConstructor();
44 this.typeName = site.getTypeName();
45 this.functionName = site.getFunctionName();
46 this.methodName = site.getMethodName();
47 } while (
48 idx > offset &&
49 this.functionName === null &&
50 this.methodName === null &&
51 !isNodeInternalSite((site = stack[--idx]))
52 );
53 }
54 toString() {
55 const prefix = this.isConstructor ? 'new ' : '';
56 const namePrefix =
57 this.typeName !== null && this.typeName !== 'global'
58 ? `${this.typeName}.`
59 : '';
60 const name = `${namePrefix}${this.functionName || this.methodName || '<anonymous>'}`;
61 let location = this.fileName || '<anonymous>';
62 if (this.lineNumber != null) location += `:${this.lineNumber}`;
63 if (this.columnNumber != null) location += `:${this.columnNumber}`;
64 return `${prefix}${name}${!!name ? ' (' : ''}${location}${!!name ? ')' : ''}`;
65 }
66}
67
68export function getStackFrame(offset = 0): StackFrame | null {
69 const originalStackFormatter = Error.prepareStackTrace;
70 const originalStackTraceLimit = Error.stackTraceLimit;
71 try {
72 Error.stackTraceLimit = MAX_STACK_DEPTH + offset;
73 Error.prepareStackTrace = (_err, stack) =>
74 new StackFrame(stack, 2 + offset);
75 return new Error().stack as any;
76 } finally {
77 Error.prepareStackTrace = originalStackFormatter;
78 Error.stackTraceLimit = originalStackTraceLimit;
79 }
80}
81
82export interface PromiseWithReject<T> {
83 promise: Promise<T>;
84 reject(reason?: any): void;
85}
86
87export function promiseWithReject<T>(
88 promise: Promise<T>,
89 onSettled: () => void
90): PromiseWithReject<T> {
91 let reject: PromiseWithReject<T>['reject'];
92 return {
93 promise: new Promise<T>((resolve, _reject) => {
94 promise.then(
95 result => {
96 resolve(result);
97 onSettled();
98 },
99 reason => {
100 _reject(reason);
101 onSettled();
102 }
103 );
104 reject = _reject;
105 }),
106 reject: reject!,
107 };
108}