Something went wrong. Try again.
fork of hey-api/openapi-ts because I need some additional things
Something went wrong. Try again.
6.4 kB · 232 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233import fs from 'node:fs';import path from 'node:path';
import colors from 'ansi-colors';
import { ensureDirSync } from './fs';import { loadPackageJson } from './tsConfig';
type IJobError = { error: Error; jobIndex: number;};
/** * Represents a single configuration error. * * Used for reporting issues with a specific config instance. */export class ConfigError extends Error { constructor(message: string) { super(message); this.name = 'ConfigError'; }}
/** * Aggregates multiple config errors with their job indices for reporting. */export class ConfigValidationError extends Error { readonly errors: ReadonlyArray<IJobError>;
constructor(errors: Array<IJobError>) { super(`Found ${errors.length} configuration ${errors.length === 1 ? 'error' : 'errors'}.`); this.name = 'ConfigValidationError'; this.errors = errors; }}
/** * Represents a runtime error originating from a specific job. * * Used for reporting job-level failures that are not config validation errors. */export class JobError extends Error { readonly originalError: IJobError;
constructor(message: string, error: IJobError) { super(message); this.name = 'JobError'; this.originalError = error; }}
export class HeyApiError extends Error { args: ReadonlyArray<unknown>; event: string; pluginName: string;
constructor({ args, error, event, name, pluginName, }: { args: unknown[]; error: Error; event: string; name: string; pluginName: string; }) { const message = error instanceof Error ? error.message : 'Unknown error'; super(message);
this.args = args; this.cause = error.cause; this.event = event; this.name = name || error.name; this.pluginName = pluginName; this.stack = error.stack; }}
export function logCrashReport(error: unknown, logsDir: string): string | undefined { if (error instanceof ConfigError || error instanceof ConfigValidationError) { return; }
if (error instanceof JobError) { error = error.originalError.error; }
const logName = `openapi-ts-error-${Date.now()}.log`; const fullDir = path.resolve(process.cwd(), logsDir); ensureDirSync(fullDir); const logPath = path.resolve(fullDir, logName);
let logContent = `[${new Date().toISOString()}] `;
if (error instanceof HeyApiError) { logContent += `${error.name} during event "${error.event}"\n`; if (error.pluginName) { logContent += `Plugin: ${error.pluginName}\n`; } logContent += `Arguments: ${JSON.stringify(error.args, null, 2)}\n\n`; }
const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined;
logContent += `Error: ${message}\n`; if (stack) { logContent += `Stack:\n${stack}\n`; }
fs.writeFileSync(logPath, logContent);
return logPath;}
export async function openGitHubIssueWithCrashReport( error: unknown, initialDir: string,): Promise<void> { const packageJson = loadPackageJson(initialDir); if (!packageJson?.bugs.url) return;
if (error instanceof JobError) { error = error.originalError.error; }
let body = '';
if (error instanceof HeyApiError) { if (error.pluginName) { body += `**Plugin**: \`${error.pluginName}\`\n`; } body += `**Event**: \`${error.event}\`\n`; body += `**Arguments**:\n\`\`\`ts\n${JSON.stringify(error.args, null, 2)}\n\`\`\`\n\n`; }
const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined;
body += `**Error**: \`${message}\`\n`; if (stack) { body += `\n**Stack Trace**:\n\`\`\`\n${stack}\n\`\`\``; }
const search = new URLSearchParams({ body, labels: 'bug 🔥', title: 'Crash Report', }); const url = `${packageJson.bugs.url}new?${search.toString()}`; const open = (await import('open')).default; await open(url);}
export function printCrashReport({ error, logPath,}: { error: unknown; logPath: string | undefined;}): void { if (error instanceof ConfigValidationError && error.errors.length) { const groupByJob = new Map<number, Array<Error>>(); for (const { error: err, jobIndex } of error.errors) { if (!groupByJob.has(jobIndex)) { groupByJob.set(jobIndex, []); } groupByJob.get(jobIndex)!.push(err); }
for (const [jobIndex, errors] of groupByJob.entries()) { const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); const count = errors.length; const baseString = colors.red( `Found ${count} configuration ${count === 1 ? 'error' : 'errors'}:`, ); console.error(`${jobPrefix}❗️ ${baseString}`); errors.forEach((err, index) => { const itemPrefixStr = ` [${index + 1}] `; const itemPrefix = colors.red(itemPrefixStr); console.error(`${jobPrefix}${itemPrefix}${colors.white(err.message)}`); }); } } else { let jobPrefix = colors.gray('[root] '); if (error instanceof JobError) { jobPrefix = colors.gray(`[Job ${error.originalError.jobIndex + 1}] `); error = error.originalError.error; }
const baseString = colors.red('Failed with the message:'); console.error(`${jobPrefix}❌ ${baseString}`); const itemPrefixStr = ` `; const itemPrefix = colors.red(itemPrefixStr); console.error( `${jobPrefix}${itemPrefix}${typeof error === 'string' ? error : error instanceof Error ? error.message : 'Unknown error'}`, ); }
if (logPath) { const jobPrefix = colors.gray('[root] '); console.error(`${jobPrefix}${colors.cyan('📄 Crash log saved to:')} ${colors.gray(logPath)}`); }}
export async function shouldReportCrash({ error, isInteractive,}: { error: unknown; isInteractive: boolean | undefined;}): Promise<boolean> { if (!isInteractive || error instanceof ConfigError || error instanceof ConfigValidationError) { return false; }
return new Promise((resolve) => { const jobPrefix = colors.gray('[root] '); console.log( `${jobPrefix}${colors.yellow('📢 Open a GitHub issue with crash details? (y/N):')}`, ); process.stdin.setEncoding('utf8'); process.stdin.once('data', (data: string) => { resolve(data.trim().toLowerCase() === 'y'); }); });}