UNPKG

@embeddable.com/sdk-core

Version:

Core Embeddable SDK module responsible for web-components bundling and publishing.

173 lines (151 loc) 4.78 kB
import * as fs from "node:fs"; import * as fsp from "node:fs/promises"; import * as path from "node:path"; const ANSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g; export type CycleStatus = "ok" | "error"; export type Scope = | "embeddable" | "dataModel" | "securityContext" | "clientContext" | "push" | "hmr"; export type IssueStage = "validate" | "sync"; export interface ValidationIssuePayload { scope: Scope; filePath: string; message: string; line?: number; column?: number; path?: string; stage?: IssueStage; } export interface DevLoggerInitOptions { logFile?: string; eventsFile?: string; } interface PatchedWrite { __devLoggerPatched?: true; } const stripAnsi = (input: unknown): string => { if (typeof input === "string") { return input.replaceAll(ANSI_REGEX, ""); } if (input instanceof Uint8Array) { return Buffer.from(input).toString("utf8").replaceAll(ANSI_REGEX, ""); } return ""; }; class DevLogger { private logStream?: fs.WriteStream; private eventsStream?: fs.WriteStream; private cycleCounter = 0; private originalStdoutWrite?: typeof process.stdout.write; private originalStderrWrite?: typeof process.stderr.write; async init(opts: DevLoggerInitOptions): Promise<void> { if (opts.logFile) { this.logStream = await this.openStream(opts.logFile); this.installMirror(this.logStream); } if (opts.eventsFile) { this.eventsStream = await this.openStream(opts.eventsFile); } } async close(): Promise<void> { this.uninstallMirror(); await Promise.all([ this.endStream(this.logStream), this.endStream(this.eventsStream), ]); this.logStream = undefined; this.eventsStream = undefined; } marker(event: string, payload: Record<string, unknown> = {}): void { this.writeEvent({ type: "marker", event, ...payload }); } issue(payload: ValidationIssuePayload): void { if (!this.eventsStream) return; const { filePath, column, ...rest } = payload; const obj: Record<string, unknown> = { type: "issue", event: "validation_error", file: filePath, ...rest, }; if (column !== undefined) obj.col = column; this.writeEvent(obj); } startCycle(scope: Scope, payload: Record<string, unknown> = {}): number { const cycle = ++this.cycleCounter; this.marker("validate_start", { cycle, scope, ...payload }); return cycle; } endCycle( cycleId: number, scope: Scope, status: CycleStatus, payload: Record<string, unknown> = {}, ): void { this.marker("validate_end", { cycle: cycleId, scope, status, ...payload }); } private writeEvent(obj: Record<string, unknown>): void { if (!this.eventsStream) return; const line = JSON.stringify({ ts: new Date().toISOString(), ...obj }); this.eventsStream.write(line + "\n"); } private async openStream(filePath: string): Promise<fs.WriteStream> { const resolved = path.resolve(process.cwd(), filePath); await fsp.mkdir(path.dirname(resolved), { recursive: true }); return fs.createWriteStream(resolved, { flags: "w" }); } private installMirror(stream: fs.WriteStream): void { this.originalStdoutWrite = process.stdout.write; this.originalStderrWrite = process.stderr.write; const wrap = (orig: typeof process.stdout.write, target: NodeJS.WriteStream) => (chunk: unknown, encoding?: unknown, cb?: unknown): boolean => { try { stream.write(stripAnsi(chunk)); } catch { // never let mirror errors break the dev server } return (orig as (...args: unknown[]) => boolean).call( target, chunk, encoding, cb, ); }; const stdoutPatch = wrap( this.originalStdoutWrite, process.stdout, ) as typeof process.stdout.write & PatchedWrite; const stderrPatch = wrap( this.originalStderrWrite, process.stderr, ) as typeof process.stderr.write & PatchedWrite; stdoutPatch.__devLoggerPatched = true; stderrPatch.__devLoggerPatched = true; process.stdout.write = stdoutPatch; process.stderr.write = stderrPatch; } private uninstallMirror(): void { if (this.originalStdoutWrite) { process.stdout.write = this.originalStdoutWrite; this.originalStdoutWrite = undefined; } if (this.originalStderrWrite) { process.stderr.write = this.originalStderrWrite; this.originalStderrWrite = undefined; } } private endStream(stream?: fs.WriteStream): Promise<void> { if (!stream) return Promise.resolve(); return new Promise((resolve) => { stream.end(() => resolve()); }); } } const devLogger = new DevLogger(); export default devLogger; export { stripAnsi };