UNPKG

@embeddable.com/sdk-core

Version:

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

184 lines (158 loc) 5.37 kB
import { describe, it, expect, afterEach } from "vitest"; import * as fs from "node:fs"; import * as fsp from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import devLogger, { stripAnsi } from "./devLogger"; const makeTempPath = (name: string) => path.join(os.tmpdir(), `devlogger-${process.pid}-${Date.now()}-${name}`); const readNdjson = (file: string): Record<string, unknown>[] => { const raw = fs.readFileSync(file, "utf8"); return raw .split("\n") .filter((l) => l.length > 0) .map((l) => JSON.parse(l)); }; const tracked: string[] = []; const trackedFile = (name: string) => { const p = makeTempPath(name); tracked.push(p); return p; }; afterEach(async () => { await devLogger.close(); while (tracked.length) { const p = tracked.pop()!; try { await fsp.unlink(p); } catch { // ignore } } }); describe("stripAnsi", () => { it("removes CSI escape sequences", () => { expect(stripAnsi("\x1B[31mhello\x1B[0m")).toBe("hello"); expect(stripAnsi("\x1B[2K\x1B[1G✔ done")).toBe("✔ done"); }); it("leaves plain text untouched", () => { expect(stripAnsi("plain")).toBe("plain"); expect(stripAnsi("")).toBe(""); }); it("accepts buffers", () => { expect(stripAnsi(Buffer.from("\x1B[31mhi\x1B[0m"))).toBe("hi"); }); }); describe("devLogger no-op when no flags", () => { it("does not emit anything when events-file is unset", async () => { const before = process.stdout.write; await devLogger.init({}); devLogger.marker("anything"); devLogger.issue({ scope: "embeddable", filePath: "/x.yml", message: "noop", }); devLogger.startCycle("embeddable"); devLogger.endCycle(1, "embeddable", "ok"); expect(process.stdout.write).toBe(before); await devLogger.close(); }); }); describe("devLogger events file", () => { it("writes one valid JSON object per line for marker and issue", async () => { const eventsFile = trackedFile("events.ndjson"); await devLogger.init({ eventsFile }); devLogger.marker("custom_event", { scope: "embeddable", foo: "bar" }); devLogger.issue({ scope: "embeddable", stage: "validate", filePath: "/abs/foo.embeddable.yml", message: "Required", line: 12, column: 3, path: "embeddables[0].name", }); await devLogger.close(); const lines = readNdjson(eventsFile); expect(lines).toHaveLength(2); const [marker, issue] = lines; expect(marker).toMatchObject({ type: "marker", event: "custom_event", scope: "embeddable", foo: "bar", }); expect(typeof marker.ts).toBe("string"); expect(issue).toMatchObject({ type: "issue", event: "validation_error", scope: "embeddable", stage: "validate", file: "/abs/foo.embeddable.yml", message: "Required", line: 12, col: 3, path: "embeddables[0].name", }); }); it("emits matched validate_start/validate_end with the same cycle id", async () => { const eventsFile = trackedFile("cycle.ndjson"); await devLogger.init({ eventsFile }); const cycle = devLogger.startCycle("embeddable", { files: ["/a.yml"] }); devLogger.endCycle(cycle, "embeddable", "ok"); await devLogger.close(); const lines = readNdjson(eventsFile); expect(lines).toHaveLength(2); expect(lines[0]).toMatchObject({ type: "marker", event: "validate_start", scope: "embeddable", cycle, files: ["/a.yml"], }); expect(lines[1]).toMatchObject({ type: "marker", event: "validate_end", scope: "embeddable", cycle, status: "ok", }); }); it("preserves monotonic cycle ids across consecutive cycles", async () => { const eventsFile = trackedFile("monotonic.ndjson"); await devLogger.init({ eventsFile }); const c1 = devLogger.startCycle("embeddable"); devLogger.endCycle(c1, "embeddable", "ok"); const c2 = devLogger.startCycle("embeddable"); devLogger.endCycle(c2, "embeddable", "error", { stage: "validate" }); expect(c2).toBe(c1 + 1); await devLogger.close(); }); }); describe("devLogger TTY mirror", () => { it("patches stdout/stderr while a log file is active and writes ANSI-stripped copy", async () => { const logFile = trackedFile("mirror.log"); const origStdout = process.stdout.write; const origStderr = process.stderr.write; await devLogger.init({ logFile }); expect(process.stdout.write).not.toBe(origStdout); expect(process.stderr.write).not.toBe(origStderr); process.stdout.write("\x1B[31mhello\x1B[0m\n"); process.stderr.write("\x1B[2K\x1B[1G✔ done\n"); await devLogger.close(); expect(process.stdout.write).toBe(origStdout); expect(process.stderr.write).toBe(origStderr); const content = fs.readFileSync(logFile, "utf8"); expect(content).toContain("hello\n"); expect(content).toContain("✔ done\n"); expect(content).not.toContain("\x1B"); }); it("does not patch stdout when only events-file is set", async () => { const eventsFile = trackedFile("events-only.ndjson"); const origStdout = process.stdout.write; await devLogger.init({ eventsFile }); expect(process.stdout.write).toBe(origStdout); await devLogger.close(); }); });