vite-plugin-symfony
Version:
A Vite plugin to integrate easily Vite in your Symfony application
167 lines (150 loc) • 4.58 kB
text/typescript
/* eslint no-console: 0 */
import readline from "node:readline";
import colors from "picocolors";
import type { RollupError } from "rollup";
export interface ResolvedServerUrls {
local: string[];
network: string[];
}
export type LogType = "error" | "warn" | "info";
export type LogLevel = LogType | "silent";
export interface Logger {
info(msg: string, options?: LogOptions): void;
warn(msg: string, options?: LogOptions): void;
warnOnce(msg: string, options?: LogOptions): void;
error(msg: string, options?: LogErrorOptions): void;
clearScreen(type: LogType): void;
hasErrorLogged(error: Error | RollupError): boolean;
hasWarned: boolean;
}
export interface LogOptions {
clear?: boolean;
timestamp?: boolean;
}
export interface LogErrorOptions extends LogOptions {
error?: Error | RollupError | null;
}
export const LogLevels: Record<LogLevel, number> = {
silent: 0,
error: 1,
warn: 2,
info: 3,
};
let lastType: LogType | undefined;
let lastMsg: string | undefined;
let sameCount = 0;
function clearScreen() {
const repeatCount = process.stdout.rows - 2;
const blank = repeatCount > 0 ? "\n".repeat(repeatCount) : "";
console.log(blank);
readline.cursorTo(process.stdout, 0, 0);
readline.clearScreenDown(process.stdout);
}
export interface LoggerOptions {
prefix?: string;
allowClearScreen?: boolean;
customLogger?: Logger;
}
export function createLogger(level: LogLevel = "info", options: LoggerOptions = {}): Logger {
if (options.customLogger) {
return options.customLogger;
}
const timeFormatter = new Intl.DateTimeFormat(undefined, {
hour: "numeric",
minute: "numeric",
second: "numeric",
});
const loggedErrors = new WeakSet<Error | RollupError>();
const { prefix = "[vite]", allowClearScreen = true } = options;
const thresh = LogLevels[level];
const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI;
const clear = canClearScreen ? clearScreen : () => {};
function output(type: LogType, msg: string, options: LogErrorOptions = {}) {
if (thresh >= LogLevels[type]) {
const method = type === "info" ? "log" : type;
const format = () => {
const tag =
type === "info"
? colors.cyan(colors.bold(prefix))
: type === "warn"
? colors.yellow(colors.bold(prefix))
: colors.red(colors.bold(prefix));
if (options.timestamp) {
return `${colors.dim(timeFormatter.format(new Date()))} ${tag} ${msg}`;
} else {
return `${tag} ${msg}`;
}
};
if (options.error) {
loggedErrors.add(options.error);
}
if (canClearScreen) {
if (type === lastType && msg === lastMsg) {
sameCount++;
clear();
console[method](format(), colors.yellow(`(x${sameCount + 1})`));
} else {
sameCount = 0;
lastMsg = msg;
lastType = type;
if (options.clear) {
clear();
}
console[method](format());
}
} else {
console[method](format());
}
}
}
const warnedMessages = new Set<string>();
const logger: Logger = {
hasWarned: false,
info(msg, opts) {
output("info", msg, opts);
},
warn(msg, opts) {
logger.hasWarned = true;
output("warn", msg, opts);
},
warnOnce(msg, opts) {
if (warnedMessages.has(msg)) return;
logger.hasWarned = true;
output("warn", msg, opts);
warnedMessages.add(msg);
},
error(msg, opts) {
logger.hasWarned = true;
output("error", msg, opts);
},
clearScreen(type) {
if (thresh >= LogLevels[type]) {
clear();
}
},
hasErrorLogged(error) {
return loggedErrors.has(error);
},
};
return logger;
}
export function printServerUrls(
urls: ResolvedServerUrls,
optionsHost: string | boolean | undefined,
info: Logger["info"],
): void {
const colorUrl = (url: string) => colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`));
for (const url of urls.local) {
info(` ${colors.green("➜")} ${colors.bold("Local")}: ${colorUrl(url)}`);
}
for (const url of urls.network) {
info(` ${colors.green("➜")} ${colors.bold("Network")}: ${colorUrl(url)}`);
}
if (urls.network.length === 0 && optionsHost === undefined) {
info(
colors.dim(` ${colors.green("➜")} ${colors.bold("Network")}: use `) +
colors.bold("--host") +
colors.dim(" to expose"),
);
}
}