UNPKG

everything-dev

Version:

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

1 lines • 14 kB
{"version":3,"file":"dev-log-pipeline.cjs","names":[],"sources":["../src/dev-log-pipeline.ts"],"sourcesContent":["const ESC = \"\\x1b\";\nconst BEL = \"\\x07\";\nconst ANSI_RE = new RegExp(`${ESC}\\\\[[0-?]*[ -/]*[@-~]|${ESC}\\\\][^${BEL}]*${BEL}`, \"g\");\n\nexport const stripAnsi = (input: string): string => input.replace(ANSI_RE, \"\");\n\nexport type LogLevel = \"error\" | \"warn\" | \"info\" | \"debug\";\n\nexport type LogCategory = \"banner\" | \"build\" | \"db\" | \"mf\" | \"lifecycle\" | \"shutdown\" | \"other\";\n\nexport interface RawLogLine {\n source: string;\n line: string;\n isError?: boolean;\n timestamp?: number;\n}\n\nexport interface LogEvent {\n timestamp: number;\n source: string;\n line: string;\n isError: boolean;\n level: LogLevel;\n category: LogCategory;\n}\n\nconst LEVEL_RANK: Record<LogLevel, number> = { error: 0, warn: 1, info: 2, debug: 3 };\n\nconst LOG_LEVELS: readonly LogLevel[] = [\"error\", \"warn\", \"info\", \"debug\"];\n\nexport interface LogLevelEnv {\n BOS_LOG_LEVEL?: string;\n DEBUG?: string;\n}\n\nexport function isDebug(value: string | undefined): boolean {\n return value === \"true\" || value === \"1\";\n}\n\nexport function resolveLogLevel(\n env: LogLevelEnv = process.env as LogLevelEnv,\n flag?: string,\n): LogLevel {\n if (flag && (LOG_LEVELS as readonly string[]).includes(flag)) return flag as LogLevel;\n if (env.BOS_LOG_LEVEL && (LOG_LEVELS as readonly string[]).includes(env.BOS_LOG_LEVEL)) {\n return env.BOS_LOG_LEVEL as LogLevel;\n }\n if (isDebug(env.DEBUG)) return \"debug\";\n return \"warn\";\n}\n\nexport type EffectLogLevel = \"Error\" | \"Warn\" | \"Info\" | \"Debug\";\n\nexport function toEffectLogLevel(level: LogLevel): EffectLogLevel {\n switch (level) {\n case \"error\":\n return \"Error\";\n case \"warn\":\n return \"Warn\";\n case \"info\":\n return \"Info\";\n case \"debug\":\n return \"Debug\";\n }\n}\n\nconst STACK_FRAME_RE = /^\\s+at\\s/;\n\nconst countBraces = (line: string): number => {\n let count = 0;\n for (const ch of line) {\n if (ch === \"{\") count += 1;\n else if (ch === \"}\") count -= 1;\n }\n return count;\n};\n\ninterface PendingMeta {\n source: string;\n isError?: boolean;\n timestamp?: number;\n}\n\ninterface NormalizerState {\n pending: string[];\n openBraces: number;\n meta?: PendingMeta;\n}\n\nconst makeNormalizerState = (): NormalizerState => ({ pending: [], openBraces: 0 });\n\nconst pushLine = (state: NormalizerState, line: string) => {\n state.pending.push(line);\n state.openBraces = Math.max(0, state.openBraces + countBraces(line));\n};\n\nconst resetState = (state: NormalizerState) => {\n state.pending = [];\n state.openBraces = 0;\n};\n\ninterface CompletedEvent {\n text: string;\n meta: PendingMeta;\n}\n\nconst feedLine = (\n state: NormalizerState,\n line: string,\n meta: PendingMeta,\n): CompletedEvent | null => {\n if (state.pending.length === 0) {\n state.meta = meta;\n pushLine(state, line);\n return null;\n }\n\n const isContinuation = state.openBraces > 0 || STACK_FRAME_RE.test(line) || /^\\s*\\{/.test(line);\n\n if (isContinuation) {\n const closeOnBalance = state.openBraces > 0;\n pushLine(state, line);\n if (closeOnBalance && state.openBraces === 0) {\n const completed = { text: state.pending.join(\"\\n\"), meta: state.meta! };\n resetState(state);\n return completed;\n }\n return null;\n }\n\n const completed = { text: state.pending.join(\"\\n\"), meta: state.meta! };\n resetState(state);\n state.meta = meta;\n pushLine(state, line);\n return completed;\n};\n\nconst drainState = (state: NormalizerState): CompletedEvent | null => {\n if (state.pending.length === 0) return null;\n const completed = { text: state.pending.join(\"\\n\"), meta: state.meta! };\n resetState(state);\n return completed;\n};\n\nexport function normalizeLines(lines: string[]): string[] {\n const state = makeNormalizerState();\n const events: string[] = [];\n for (const line of lines) {\n const completed = feedLine(state, line, { source: \"\" });\n if (completed !== null) events.push(completed.text);\n }\n const last = drainState(state);\n if (last !== null) events.push(last.text);\n return events;\n}\n\nconst WARN_RE = /(?:^|\\s)WARN\\b|\\[WARN\\]|(?:^|\\s)\\w*Warning:/i;\nconst DEBUG_RE = /(?:^|\\s)DEBUG\\b|\\[DEBUG\\]/i;\n\n// rspack/rsbuild write the \"Build error:\" title to stderr with an empty\n// message and the actual diagnostics (`File: …`, `× …`) to stdout. Promote\n// those diagnostic lines so they pass the default warn display filter.\nconst BUILD_DIAGNOSTIC_RE = /^(?:File: |\\s*[×✖]\\s)/;\n\nconst EXIT_LINE_RE =\n /^Process exited (?:after|before) ready \\((?:exit code: (\\d+)|signal: ([A-Z]+))\\)/;\n\nconst CLEAN_SIGNALS = new Set([\"SIGTERM\", \"SIGINT\"]);\n\nconst CATEGORY_PATTERNS: Array<{ category: LogCategory; patterns: RegExp[] }> = [\n {\n category: \"shutdown\",\n patterns: [EXIT_LINE_RE, /\\[SIGTERM\\]/i, /shutting down/i, /graceful shutdown/i],\n },\n {\n category: \"mf\",\n patterns: [\n /\\[ ?Federation Runtime ?\\]/,\n /\\[MF\\]/,\n /Module Federation/i,\n /Executing an Effect versioned/,\n /you may want to dedupe the effect dependencies/,\n /\\[IntegrityMonitor\\]/,\n /\\[Plugins\\]/,\n ],\n },\n {\n category: \"db\",\n patterns: [/\\[Database\\]/i, /\\bpostgres\\b/i, /\\bdrizzle\\b/i, /\\bpglite\\b/i],\n },\n {\n category: \"banner\",\n patterns: [/🚀|📡|📖|💚/, /➜/, /^[┌┐└┘│─┬┴├┤╔╗╚╝║═]/],\n },\n {\n category: \"build\",\n patterns: [\n /rspack\\.config\\.js not found/,\n /rspack|rsbuild/i,\n /compiled (?:success|with|in)/i,\n /built in \\d+/i,\n /webpack/i,\n /\\bHMR\\b/,\n /building for production/i,\n BUILD_DIAGNOSTIC_RE,\n ],\n },\n {\n category: \"lifecycle\",\n patterns: [/\\bready in\\b/i, /listening (?:on|at)/i, /server (?:is )?running/i],\n },\n];\n\nconst classifyCategory = (text: string): LogCategory => {\n for (const { category, patterns } of CATEGORY_PATTERNS) {\n if (patterns.some((pattern) => pattern.test(text))) return category;\n }\n return \"other\";\n};\n\nexport function classifyEvent(raw: RawLogLine): LogEvent {\n const text = stripAnsi(raw.line);\n const exitMatch = text.match(EXIT_LINE_RE);\n const category = classifyCategory(text);\n\n let level: LogLevel;\n let isError = raw.isError === true;\n\n if (exitMatch) {\n if (exitMatch[2]) {\n level = CLEAN_SIGNALS.has(exitMatch[2]) ? \"info\" : \"error\";\n } else {\n level = exitMatch[1] === \"0\" ? \"info\" : \"error\";\n }\n isError = level === \"error\";\n } else if (BUILD_DIAGNOSTIC_RE.test(text)) {\n level = \"error\";\n isError = true;\n } else if (WARN_RE.test(text)) {\n level = \"warn\";\n isError = false;\n } else if (DEBUG_RE.test(text)) {\n level = \"debug\";\n isError = false;\n } else if (isError) {\n level = \"error\";\n } else {\n level = \"info\";\n }\n\n return {\n timestamp: raw.timestamp ?? Date.now(),\n source: raw.source,\n line: text,\n isError,\n level,\n category,\n };\n}\n\nexport function passesLevel(event: LogEvent, level: LogLevel): boolean {\n return LEVEL_RANK[event.level] <= LEVEL_RANK[level];\n}\n\nexport interface CollapsedDbEvent extends LogEvent {\n collapsed: boolean;\n}\n\nclass DbCollapser {\n private runs = new Set<string>();\n\n feed(event: LogEvent): CollapsedDbEvent | null {\n if (event.category === \"db\" && event.level !== \"error\") {\n if (!this.runs.has(event.source)) {\n this.runs.add(event.source);\n return { ...event, line: \"db ready\", isError: false, level: \"info\", collapsed: true };\n }\n return null;\n }\n this.runs.delete(event.source);\n return { ...event, collapsed: false };\n }\n}\n\nexport interface LogPipelineSinks {\n display: (event: LogEvent) => void;\n file: (event: LogEvent) => void;\n export: (event: LogEvent) => void;\n}\n\nexport interface LogPipeline {\n ingest: (raw: RawLogLine) => void;\n flush: () => void;\n}\n\nexport function createLogPipeline(options: {\n level?: LogLevel;\n sinks: LogPipelineSinks;\n}): LogPipeline {\n const level = options.level ?? resolveLogLevel();\n const sinks = options.sinks;\n const normalizers = new Map<string, NormalizerState>();\n const collapser = new DbCollapser();\n\n const stateFor = (source: string): NormalizerState => {\n let state = normalizers.get(source);\n if (!state) {\n state = makeNormalizerState();\n normalizers.set(source, state);\n }\n return state;\n };\n\n const emit = (meta: PendingMeta, text: string) => {\n const event = classifyEvent({\n source: meta.source,\n line: text,\n isError: meta.isError,\n timestamp: meta.timestamp,\n });\n sinks.file(event);\n if (!passesLevel(event, level)) return;\n const collapsed = collapser.feed(event);\n if (!collapsed) return;\n sinks.export(collapsed);\n sinks.display(collapsed);\n };\n\n return {\n ingest: (raw) => {\n const clean = stripAnsi(raw.line);\n if (!clean.trim()) return;\n const state = stateFor(raw.source);\n const completed = feedLine(state, clean, {\n source: raw.source,\n isError: raw.isError,\n timestamp: raw.timestamp,\n });\n if (completed !== null) emit(completed.meta, completed.text);\n },\n flush: () => {\n for (const state of normalizers.values()) {\n const completed = drainState(state);\n if (completed !== null) emit(completed.meta, completed.text);\n }\n },\n };\n}\n"],"mappings":";AAAA,MAAM,MAAM;AACZ,MAAM,MAAM;AACZ,MAAM,UAAU,IAAI,OAAO,GAAG,IAAI,uBAAuB,IAAI,OAAO,IAAI,IAAI,OAAO,GAAG;AAEtF,MAAa,aAAa,UAA0B,MAAM,QAAQ,SAAS,EAAE;AAsB7E,MAAM,aAAuC;CAAE,OAAO;CAAG,MAAM;CAAG,MAAM;CAAG,OAAO;AAAE;AAEpF,MAAM,aAAkC;CAAC;CAAS;CAAQ;CAAQ;AAAO;AAOzE,SAAgB,QAAQ,OAAoC;CAC1D,OAAO,UAAU,UAAU,UAAU;AACvC;AAEA,SAAgB,gBACd,MAAmB,QAAQ,KAC3B,MACU;CACV,IAAI,QAAS,WAAiC,SAAS,IAAI,GAAG,OAAO;CACrE,IAAI,IAAI,iBAAkB,WAAiC,SAAS,IAAI,aAAa,GACnF,OAAO,IAAI;CAEb,IAAI,QAAQ,IAAI,KAAK,GAAG,OAAO;CAC/B,OAAO;AACT;AAIA,SAAgB,iBAAiB,OAAiC;CAChE,QAAQ,OAAR;EACE,KAAK,SACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO;CACX;AACF;AAEA,MAAM,iBAAiB;AAEvB,MAAM,eAAe,SAAyB;CAC5C,IAAI,QAAQ;CACZ,KAAK,MAAM,MAAM,MACf,IAAI,OAAO,KAAK,SAAS;MACpB,IAAI,OAAO,KAAK,SAAS;CAEhC,OAAO;AACT;AAcA,MAAM,6BAA8C;CAAE,SAAS,CAAC;CAAG,YAAY;AAAE;AAEjF,MAAM,YAAY,OAAwB,SAAiB;CACzD,MAAM,QAAQ,KAAK,IAAI;CACvB,MAAM,aAAa,KAAK,IAAI,GAAG,MAAM,aAAa,YAAY,IAAI,CAAC;AACrE;AAEA,MAAM,cAAc,UAA2B;CAC7C,MAAM,UAAU,CAAC;CACjB,MAAM,aAAa;AACrB;AAOA,MAAM,YACJ,OACA,MACA,SAC0B;CAC1B,IAAI,MAAM,QAAQ,WAAW,GAAG;EAC9B,MAAM,OAAO;EACb,SAAS,OAAO,IAAI;EACpB,OAAO;CACT;CAIA,IAFuB,MAAM,aAAa,KAAK,eAAe,KAAK,IAAI,KAAK,SAAS,KAAK,IAAI,GAE1E;EAClB,MAAM,iBAAiB,MAAM,aAAa;EAC1C,SAAS,OAAO,IAAI;EACpB,IAAI,kBAAkB,MAAM,eAAe,GAAG;GAC5C,MAAM,YAAY;IAAE,MAAM,MAAM,QAAQ,KAAK,IAAI;IAAG,MAAM,MAAM;GAAM;GACtE,WAAW,KAAK;GAChB,OAAO;EACT;EACA,OAAO;CACT;CAEA,MAAM,YAAY;EAAE,MAAM,MAAM,QAAQ,KAAK,IAAI;EAAG,MAAM,MAAM;CAAM;CACtE,WAAW,KAAK;CAChB,MAAM,OAAO;CACb,SAAS,OAAO,IAAI;CACpB,OAAO;AACT;AAEA,MAAM,cAAc,UAAkD;CACpE,IAAI,MAAM,QAAQ,WAAW,GAAG,OAAO;CACvC,MAAM,YAAY;EAAE,MAAM,MAAM,QAAQ,KAAK,IAAI;EAAG,MAAM,MAAM;CAAM;CACtE,WAAW,KAAK;CAChB,OAAO;AACT;AAcA,MAAM,UAAU;AAChB,MAAM,WAAW;AAKjB,MAAM,sBAAsB;AAE5B,MAAM,eACJ;AAEF,MAAM,gCAAgB,IAAI,IAAI,CAAC,WAAW,QAAQ,CAAC;AAEnD,MAAM,oBAA0E;CAC9E;EACE,UAAU;EACV,UAAU;GAAC;GAAc;GAAgB;GAAkB;EAAoB;CACjF;CACA;EACE,UAAU;EACV,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CACA;EACE,UAAU;EACV,UAAU;GAAC;GAAiB;GAAiB;GAAgB;EAAa;CAC5E;CACA;EACE,UAAU;EACV,UAAU;GAAC;GAAe;GAAK;EAAqB;CACtD;CACA;EACE,UAAU;EACV,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CACA;EACE,UAAU;EACV,UAAU;GAAC;GAAiB;GAAwB;EAAyB;CAC/E;AACF;AAEA,MAAM,oBAAoB,SAA8B;CACtD,KAAK,MAAM,EAAE,UAAU,cAAc,mBACnC,IAAI,SAAS,MAAM,YAAY,QAAQ,KAAK,IAAI,CAAC,GAAG,OAAO;CAE7D,OAAO;AACT;AAEA,SAAgB,cAAc,KAA2B;CACvD,MAAM,OAAO,UAAU,IAAI,IAAI;CAC/B,MAAM,YAAY,KAAK,MAAM,YAAY;CACzC,MAAM,WAAW,iBAAiB,IAAI;CAEtC,IAAI;CACJ,IAAI,UAAU,IAAI,YAAY;CAE9B,IAAI,WAAW;EACb,IAAI,UAAU,IACZ,QAAQ,cAAc,IAAI,UAAU,EAAE,IAAI,SAAS;OAEnD,QAAQ,UAAU,OAAO,MAAM,SAAS;EAE1C,UAAU,UAAU;CACtB,OAAO,IAAI,oBAAoB,KAAK,IAAI,GAAG;EACzC,QAAQ;EACR,UAAU;CACZ,OAAO,IAAI,QAAQ,KAAK,IAAI,GAAG;EAC7B,QAAQ;EACR,UAAU;CACZ,OAAO,IAAI,SAAS,KAAK,IAAI,GAAG;EAC9B,QAAQ;EACR,UAAU;CACZ,OAAO,IAAI,SACT,QAAQ;MAER,QAAQ;CAGV,OAAO;EACL,WAAW,IAAI,aAAa,KAAK,IAAI;EACrC,QAAQ,IAAI;EACZ,MAAM;EACN;EACA;EACA;CACF;AACF;AAEA,SAAgB,YAAY,OAAiB,OAA0B;CACrE,OAAO,WAAW,MAAM,UAAU,WAAW;AAC/C;AAMA,IAAM,cAAN,MAAkB;CAChB,AAAQ,uBAAO,IAAI,IAAY;CAE/B,KAAK,OAA0C;EAC7C,IAAI,MAAM,aAAa,QAAQ,MAAM,UAAU,SAAS;GACtD,IAAI,CAAC,KAAK,KAAK,IAAI,MAAM,MAAM,GAAG;IAChC,KAAK,KAAK,IAAI,MAAM,MAAM;IAC1B,OAAO;KAAE,GAAG;KAAO,MAAM;KAAY,SAAS;KAAO,OAAO;KAAQ,WAAW;IAAK;GACtF;GACA,OAAO;EACT;EACA,KAAK,KAAK,OAAO,MAAM,MAAM;EAC7B,OAAO;GAAE,GAAG;GAAO,WAAW;EAAM;CACtC;AACF;AAaA,SAAgB,kBAAkB,SAGlB;CACd,MAAM,QAAQ,QAAQ,SAAS,gBAAgB;CAC/C,MAAM,QAAQ,QAAQ;CACtB,MAAM,8BAAc,IAAI,IAA6B;CACrD,MAAM,YAAY,IAAI,YAAY;CAElC,MAAM,YAAY,WAAoC;EACpD,IAAI,QAAQ,YAAY,IAAI,MAAM;EAClC,IAAI,CAAC,OAAO;GACV,QAAQ,oBAAoB;GAC5B,YAAY,IAAI,QAAQ,KAAK;EAC/B;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,MAAmB,SAAiB;EAChD,MAAM,QAAQ,cAAc;GAC1B,QAAQ,KAAK;GACb,MAAM;GACN,SAAS,KAAK;GACd,WAAW,KAAK;EAClB,CAAC;EACD,MAAM,KAAK,KAAK;EAChB,IAAI,CAAC,YAAY,OAAO,KAAK,GAAG;EAChC,MAAM,YAAY,UAAU,KAAK,KAAK;EACtC,IAAI,CAAC,WAAW;EAChB,MAAM,OAAO,SAAS;EACtB,MAAM,QAAQ,SAAS;CACzB;CAEA,OAAO;EACL,SAAS,QAAQ;GACf,MAAM,QAAQ,UAAU,IAAI,IAAI;GAChC,IAAI,CAAC,MAAM,KAAK,GAAG;GACnB,MAAM,QAAQ,SAAS,IAAI,MAAM;GACjC,MAAM,YAAY,SAAS,OAAO,OAAO;IACvC,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,WAAW,IAAI;GACjB,CAAC;GACD,IAAI,cAAc,MAAM,KAAK,UAAU,MAAM,UAAU,IAAI;EAC7D;EACA,aAAa;GACX,KAAK,MAAM,SAAS,YAAY,OAAO,GAAG;IACxC,MAAM,YAAY,WAAW,KAAK;IAClC,IAAI,cAAc,MAAM,KAAK,UAAU,MAAM,UAAU,IAAI;GAC7D;EACF;CACF;AACF"}