import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const SRC_ROOT = path.join(REPO_ROOT, 'src-tauri', 'crates', 'tauritavern', 'src'); const APPLICATION_SRC_ROOT = path.join(REPO_ROOT, 'src-tauri', 'crates', 'tt-application', 'src'); const SCAN_ROOTS = [SRC_ROOT, APPLICATION_SRC_ROOT]; function toPosixPath(value) { return String(value).replace(/\\/g, '/'); } async function listRustFiles(dir) { const entries = await fs.readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...await listRustFiles(fullPath)); continue; } if (entry.isFile() && entry.name.endsWith('.rs')) { files.push(fullPath); } } return files; } function collectLineViolations(relPath, line, lineNumber) { const violations = []; const stripped = line.trim(); if (stripped.includes('crate::infrastructure::logging::logger')) { violations.push('logger facade import'); } if (/logger::(debug|info|warn|error)\s*\(/.test(stripped)) { violations.push('logger facade call'); } if ( relPath.startsWith('src-tauri/crates/tt-application/src/') && stripped.includes('crate::infrastructure::logging') ) { violations.push('application -> infrastructure logging'); } if (relPath.startsWith('src-tauri/crates/tauritavern/src/presentation/') && stripped.includes('crate::infrastructure::logging')) { violations.push('presentation -> infrastructure logging'); } if (relPath === 'src-tauri/crates/tauritavern/src/presentation/commands/dev_logging_commands.rs' && stripped.includes('crate::infrastructure::')) { violations.push('dev_logging_commands -> infrastructure'); } if (relPath === 'src-tauri/crates/tauritavern/src/presentation/commands/settings_commands.rs' && stripped.includes('LlmApiLogStore')) { violations.push('settings_commands -> LlmApiLogStore'); } return violations.map((kind) => ({ relPath, lineNumber, kind, snippet: stripped, })); } async function main() { try { for (const root of SCAN_ROOTS) { await fs.access(root); } } catch { console.error('[logging-boundaries] Rust source roots not found; run from repository root.'); process.exit(2); } const violations = []; for (const root of SCAN_ROOTS) { for (const filePath of await listRustFiles(root)) { const relPath = toPosixPath(path.relative(REPO_ROOT, filePath)); const text = await fs.readFile(filePath, 'utf8'); const lines = text.split(/\r?\n/); lines.forEach((line, index) => { violations.push(...collectLineViolations(relPath, line, index + 1)); }); } } if (violations.length > 0) { for (const violation of violations) { console.error(`${violation.relPath}:${violation.lineNumber}: ${violation.kind}: ${violation.snippet}`); } console.error(`\n[logging-boundaries] ${violations.length} violation(s) found.`); process.exit(1); } console.log('[logging-boundaries] clean'); } main().catch((error) => { console.error('[logging-boundaries] failed:', error); process.exit(1); });