UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

273 lines (272 loc) • 6.67 kB
//#region src/dev-log-pipeline.ts const ESC = "\x1B"; const BEL = "\x07"; const ANSI_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]|${ESC}\\][^${BEL}]*${BEL}`, "g"); const stripAnsi = (input) => input.replace(ANSI_RE, ""); const LEVEL_RANK = { error: 0, warn: 1, info: 2, debug: 3 }; const LOG_LEVELS = [ "error", "warn", "info", "debug" ]; function isDebug(value) { return value === "true" || value === "1"; } function resolveLogLevel(env = process.env, flag) { if (flag && LOG_LEVELS.includes(flag)) return flag; if (env.BOS_LOG_LEVEL && LOG_LEVELS.includes(env.BOS_LOG_LEVEL)) return env.BOS_LOG_LEVEL; if (isDebug(env.DEBUG)) return "debug"; return "warn"; } function toEffectLogLevel(level) { switch (level) { case "error": return "Error"; case "warn": return "Warn"; case "info": return "Info"; case "debug": return "Debug"; } } const STACK_FRAME_RE = /^\s+at\s/; const countBraces = (line) => { let count = 0; for (const ch of line) if (ch === "{") count += 1; else if (ch === "}") count -= 1; return count; }; const makeNormalizerState = () => ({ pending: [], openBraces: 0 }); const pushLine = (state, line) => { state.pending.push(line); state.openBraces = Math.max(0, state.openBraces + countBraces(line)); }; const resetState = (state) => { state.pending = []; state.openBraces = 0; }; const feedLine = (state, line, meta) => { if (state.pending.length === 0) { state.meta = meta; pushLine(state, line); return null; } if (state.openBraces > 0 || STACK_FRAME_RE.test(line) || /^\s*\{/.test(line)) { const closeOnBalance = state.openBraces > 0; pushLine(state, line); if (closeOnBalance && state.openBraces === 0) { const completed = { text: state.pending.join("\n"), meta: state.meta }; resetState(state); return completed; } return null; } const completed = { text: state.pending.join("\n"), meta: state.meta }; resetState(state); state.meta = meta; pushLine(state, line); return completed; }; const drainState = (state) => { if (state.pending.length === 0) return null; const completed = { text: state.pending.join("\n"), meta: state.meta }; resetState(state); return completed; }; const WARN_RE = /(?:^|\s)WARN\b|\[WARN\]|(?:^|\s)\w*Warning:/i; const DEBUG_RE = /(?:^|\s)DEBUG\b|\[DEBUG\]/i; const BUILD_DIAGNOSTIC_RE = /^(?:File: |\s*[×✖]\s)/; const EXIT_LINE_RE = /^Process exited (?:after|before) ready \((?:exit code: (\d+)|signal: ([A-Z]+))\)/; const CLEAN_SIGNALS = /* @__PURE__ */ new Set(["SIGTERM", "SIGINT"]); const CATEGORY_PATTERNS = [ { category: "shutdown", patterns: [ EXIT_LINE_RE, /\[SIGTERM\]/i, /shutting down/i, /graceful shutdown/i ] }, { category: "mf", patterns: [ /\[ ?Federation Runtime ?\]/, /\[MF\]/, /Module Federation/i, /Executing an Effect versioned/, /you may want to dedupe the effect dependencies/, /\[IntegrityMonitor\]/, /\[Plugins\]/ ] }, { category: "db", patterns: [ /\[Database\]/i, /\bpostgres\b/i, /\bdrizzle\b/i, /\bpglite\b/i ] }, { category: "banner", patterns: [ /🚀|📡|📖|💚/, /➜/, /^[┌┐└┘│─┬┴├┤╔╗╚╝║═]/ ] }, { category: "build", patterns: [ /rspack\.config\.js not found/, /rspack|rsbuild/i, /compiled (?:success|with|in)/i, /built in \d+/i, /webpack/i, /\bHMR\b/, /building for production/i, BUILD_DIAGNOSTIC_RE ] }, { category: "lifecycle", patterns: [ /\bready in\b/i, /listening (?:on|at)/i, /server (?:is )?running/i ] } ]; const classifyCategory = (text) => { for (const { category, patterns } of CATEGORY_PATTERNS) if (patterns.some((pattern) => pattern.test(text))) return category; return "other"; }; function classifyEvent(raw) { const text = stripAnsi(raw.line); const exitMatch = text.match(EXIT_LINE_RE); const category = classifyCategory(text); let level; let isError = raw.isError === true; if (exitMatch) { if (exitMatch[2]) level = CLEAN_SIGNALS.has(exitMatch[2]) ? "info" : "error"; else level = exitMatch[1] === "0" ? "info" : "error"; isError = level === "error"; } else if (BUILD_DIAGNOSTIC_RE.test(text)) { level = "error"; isError = true; } else if (WARN_RE.test(text)) { level = "warn"; isError = false; } else if (DEBUG_RE.test(text)) { level = "debug"; isError = false; } else if (isError) level = "error"; else level = "info"; return { timestamp: raw.timestamp ?? Date.now(), source: raw.source, line: text, isError, level, category }; } function passesLevel(event, level) { return LEVEL_RANK[event.level] <= LEVEL_RANK[level]; } var DbCollapser = class { runs = /* @__PURE__ */ new Set(); feed(event) { if (event.category === "db" && event.level !== "error") { if (!this.runs.has(event.source)) { this.runs.add(event.source); return { ...event, line: "db ready", isError: false, level: "info", collapsed: true }; } return null; } this.runs.delete(event.source); return { ...event, collapsed: false }; } }; function createLogPipeline(options) { const level = options.level ?? resolveLogLevel(); const sinks = options.sinks; const normalizers = /* @__PURE__ */ new Map(); const collapser = new DbCollapser(); const stateFor = (source) => { let state = normalizers.get(source); if (!state) { state = makeNormalizerState(); normalizers.set(source, state); } return state; }; const emit = (meta, text) => { const event = classifyEvent({ source: meta.source, line: text, isError: meta.isError, timestamp: meta.timestamp }); sinks.file(event); if (!passesLevel(event, level)) return; const collapsed = collapser.feed(event); if (!collapsed) return; sinks.export(collapsed); sinks.display(collapsed); }; return { ingest: (raw) => { const clean = stripAnsi(raw.line); if (!clean.trim()) return; const state = stateFor(raw.source); const completed = feedLine(state, clean, { source: raw.source, isError: raw.isError, timestamp: raw.timestamp }); if (completed !== null) emit(completed.meta, completed.text); }, flush: () => { for (const state of normalizers.values()) { const completed = drainState(state); if (completed !== null) emit(completed.meta, completed.text); } } }; } //#endregion exports.classifyEvent = classifyEvent; exports.createLogPipeline = createLogPipeline; exports.isDebug = isDebug; exports.passesLevel = passesLevel; exports.resolveLogLevel = resolveLogLevel; exports.stripAnsi = stripAnsi; exports.toEffectLogLevel = toEffectLogLevel; //# sourceMappingURL=dev-log-pipeline.cjs.map