// Native const EventEmitter = require("events"); // Packages const ansi = require("ansi-escapes"); const io = require("socket.io-client"); const chalk = require("chalk"); const { compare, deserialize } = require("./logs"); class Lines { constructor(maxLines = 100) { this.max = maxLines; this.buf = []; } write(str) { const { max, buf } = this; if (buf.length === max) { process.stdout.write(ansi.eraseLines(max + 1)); buf.shift(); buf.forEach(line => console.log(line)); } buf.push(str); console.log(str); } reset() { this.buf = []; } } module.exports = class Logger extends EventEmitter { constructor(host, { debug = false, quiet = false } = {}) { super(); this.host = host; this.debug = debug; this.quiet = quiet; // readyState this.building = false; this.socket = io(`https://io.now.sh/states?host=${host}&v=2`); this.socket.once("error", this.onSocketError.bind(this)); this.socket.on("state", this.onState.bind(this)); this.socket.on("logs", this.onLog.bind(this)); this.socket.on("backend", this.onComplete.bind(this)); this.lines = new Lines(10); // log buffer this.buf = []; } onState(state) { // console.log(state) if (!state.id) { console.error("> Deployment not found"); this.emit("error"); return; } if (state.error) { this.emit("error", state); return; } if (state.backend) { this.onComplete(); return; } if (state.logs) { state.logs.forEach(this.onLog, this); } } onLog(log) { if (!this.building) { if (!this.quiet) { console.log("> Building"); } this.building = true; } if (this.quiet) { return; } log = deserialize(log); const timer = setTimeout( () => { this.buf.sort((a, b) => compare(a.log, b.log)); const idx = this.buf.findIndex(b => b.log.id === log.id) + 1; for (const b of this.buf.slice(0, idx)) { clearTimeout(b.timer); this.printLog(b.log); } this.buf = this.buf.slice(idx); }, 300 ); this.buf.push({ log, timer }); } onComplete() { this.socket.disconnect(); if (this.building) { this.building = false; } this.buf.sort((a, b) => compare(a.log, b.log)); // flush all buffer for (const b of this.buf) { clearTimeout(b.timer); this.printLog(b.log); } this.buf = []; this.emit("close"); } onSocketError(err) { if (this.debug) { console.log(`> [debug] Socket error ${err}\n${err.stack}`); } } printLog(log) { const data = log.object ? JSON.stringify(log.object) : log.text; if (log.type === "command") { console.log(`${chalk.gray(">")} ▲ ${data}`); this.lines.reset(); } else if (log.type === "stderr") { data.split("\n").forEach(v => { if (v.length > 0) { console.error(chalk.gray(`> ${v}`)); } }); this.lines.reset(); } else if (log.type === "stdout") { data.split("\n").forEach(v => { if (v.length > 0) { this.lines.write(`${chalk.gray(">")} ${v}`); } }); } } };