From fa1fe39bb383e5716737ba6ea784e74fd382a462 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 22:39:46 +0300 Subject: [PATCH] chore: remove direct pino/pino-pretty dependency Pino was replaced with a built-in logger on main. For branches with baileys (WhatsApp), pino resolves as a transitive dependency of @whiskeysockets/baileys. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/x-integration/host.ts | 6 +-- package.json | 2 - src/logger.ts | 79 +++++++++++++++++++++++++--- src/mount-security.ts | 16 +++--- 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/.claude/skills/x-integration/host.ts b/.claude/skills/x-integration/host.ts index a56269dbf..8971f640b 100644 --- a/.claude/skills/x-integration/host.ts +++ b/.claude/skills/x-integration/host.ts @@ -8,12 +8,8 @@ import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; -import pino from 'pino'; -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } } -}); +import { logger } from '../../../src/logger.js'; interface SkillResult { success: boolean; diff --git a/package.json b/package.json index 91746b017..cad63fe90 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", "cron-parser": "5.5.0", - "pino": "^9.6.0", - "pino-pretty": "^13.0.0", "yaml": "^2.8.2", "zod": "^4.3.6" }, diff --git a/src/logger.ts b/src/logger.ts index 273dc0f0d..6b18a9b17 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,11 +1,78 @@ -import pino from 'pino'; +const LEVELS = { debug: 20, info: 30, warn: 40, error: 50, fatal: 60 } as const; +type Level = keyof typeof LEVELS; -export const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); +const COLORS: Record = { + debug: '\x1b[34m', + info: '\x1b[32m', + warn: '\x1b[33m', + error: '\x1b[31m', + fatal: '\x1b[41m\x1b[37m', +}; +const KEY_COLOR = '\x1b[35m'; +const MSG_COLOR = '\x1b[36m'; +const RESET = '\x1b[39m'; +const FULL_RESET = '\x1b[0m'; -// Route uncaught errors through pino so they get timestamps in stderr +const threshold = + LEVELS[(process.env.LOG_LEVEL as Level) || 'info'] ?? LEVELS.info; + +function formatErr(err: unknown): string { + if (err instanceof Error) { + return `{\n "type": "${err.constructor.name}",\n "message": "${err.message}",\n "stack":\n ${err.stack}\n }`; + } + return JSON.stringify(err); +} + +function formatData(data: Record): string { + let out = ''; + for (const [k, v] of Object.entries(data)) { + if (k === 'err') { + out += `\n ${KEY_COLOR}err${RESET}: ${formatErr(v)}`; + } else { + out += `\n ${KEY_COLOR}${k}${RESET}: ${JSON.stringify(v)}`; + } + } + return out; +} + +function ts(): string { + const d = new Date(); + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`; +} + +function log( + level: Level, + dataOrMsg: Record | string, + msg?: string, +): void { + if (LEVELS[level] < threshold) return; + const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`; + const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout; + if (typeof dataOrMsg === 'string') { + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`, + ); + } else { + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`, + ); + } +} + +export const logger = { + debug: (dataOrMsg: Record | string, msg?: string) => + log('debug', dataOrMsg, msg), + info: (dataOrMsg: Record | string, msg?: string) => + log('info', dataOrMsg, msg), + warn: (dataOrMsg: Record | string, msg?: string) => + log('warn', dataOrMsg, msg), + error: (dataOrMsg: Record | string, msg?: string) => + log('error', dataOrMsg, msg), + fatal: (dataOrMsg: Record | string, msg?: string) => + log('fatal', dataOrMsg, msg), +}; + +// Route uncaught errors through logger so they get timestamps in stderr process.on('uncaughtException', (err) => { logger.fatal({ err }, 'Uncaught exception'); process.exit(1); diff --git a/src/mount-security.ts b/src/mount-security.ts index 3dceea5aa..4a9eb1212 100644 --- a/src/mount-security.ts +++ b/src/mount-security.ts @@ -9,16 +9,10 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import pino from 'pino'; - import { MOUNT_ALLOWLIST_PATH } from './config.js'; +import { logger } from './logger.js'; import { AdditionalMount, AllowedRoot, MountAllowlist } from './types.js'; -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); - // Cache the allowlist in memory - only reloads on process restart let cachedAllowlist: MountAllowlist | null = null; let allowlistLoadError: string | null = null; @@ -63,7 +57,8 @@ export function loadMountAllowlist(): MountAllowlist | null { try { if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) { - allowlistLoadError = `Mount allowlist not found at ${MOUNT_ALLOWLIST_PATH}`; + // Do NOT cache this as an error — file may be created later without restart. + // Only parse/structural errors are permanently cached. logger.warn( { path: MOUNT_ALLOWLIST_PATH }, 'Mount allowlist not found - additional mounts will be BLOCKED. ' + @@ -215,6 +210,11 @@ function isValidContainerPath(containerPath: string): boolean { return false; } + // Must not contain colons — prevents Docker -v option injection (e.g., "repo:rw") + if (containerPath.includes(':')) { + return false; + } + return true; }