@embeddable.com/sdk-core
Version:
Core Embeddable SDK module responsible for web-components bundling and publishing.
184 lines (158 loc) • 5.37 kB
text/typescript
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();
});
});