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