velxio/frontend/src/utils/compilationLogger.ts

108 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* Parse a CompileResult into CompilationLog entries for display in the console.
*/
import type { CompileResult } from '../services/compilation';
/** Which run target a log line belongs to lets the console group output by
* board / chip, the way multiple Arduinos already stream. Optional: lines with
* no target (e.g. "Compiling all targets...", "Done") render as plain
* narration. */
export interface CompileTarget {
id: string;
label: string;
kind: 'board' | 'chip';
}
export interface CompilationLog {
timestamp: Date;
type: 'info' | 'success' | 'error' | 'warning' | 'core-install';
message: string;
target?: CompileTarget;
}
export function parseCompileResult(
result: CompileResult,
board: string,
target?: CompileTarget,
): CompilationLog[] {
const logs: CompilationLog[] = [];
const now = new Date();
logs.push({ timestamp: now, type: 'info', message: `Compiling for ${board}...` });
// Core install log
if (result.core_install_log) {
for (const line of result.core_install_log.split('\n')) {
if (line.trim()) {
logs.push({ timestamp: now, type: 'core-install', message: line });
}
}
}
// stdout — for ESP-IDF/ninja builds, compiler errors appear here
if (result.stdout) {
let inFailedBlock = false;
for (const line of result.stdout.split('\n')) {
if (!line.trim()) continue;
const stripped = line.trim();
// Ninja FAILED block start
if (
stripped.startsWith('FAILED:') ||
stripped === 'ninja: build stopped: subcommand failed.'
) {
inFailedBlock = true;
logs.push({ timestamp: now, type: 'error', message: line });
continue;
}
// Progress line [N/M] ends a FAILED block
if (inFailedBlock && /^\[\d+\/\d+\]/.test(stripped)) {
inFailedBlock = false;
}
// Classify the line
let type: CompilationLog['type'];
if (inFailedBlock) {
// Lines inside a FAILED block: compiler output — detect subcategory
type = /:\s*(fatal )?error:/i.test(line) ? 'error' : 'warning';
} else if (/:\s*(fatal )?error:/i.test(line) && !/^\[/.test(stripped)) {
type = 'error';
} else if (/:\s*warning:/i.test(line) || line.toLowerCase().includes('warning')) {
type = 'warning';
} else {
type = 'info';
}
logs.push({ timestamp: now, type, message: line });
}
}
// stderr — classify lines as warnings or errors
if (result.stderr) {
for (const line of result.stderr.split('\n')) {
if (!line.trim()) continue;
let type: CompilationLog['type'] = 'error';
const lower = line.toLowerCase();
if (lower.includes('warning:') || lower.includes('warn ')) {
type = 'warning';
} else if (
lower.includes('note:') ||
lower.includes('in file included') ||
lower.startsWith('using ') ||
lower.startsWith('libraries ')
) {
type = 'info';
}
logs.push({ timestamp: now, type, message: line });
}
}
// Final status
if (result.success) {
logs.push({ timestamp: now, type: 'success', message: '✓ Compilation successful' });
} else {
const errorMsg = result.error || 'Compilation failed';
logs.push({ timestamp: now, type: 'error', message: `${errorMsg}` });
}
return target ? logs.map((l) => ({ ...l, target })) : logs;
}