nodeConsole.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const truncateArgs = require("../logging/truncateArgs");
  8. const memoize = require("../util/memoize");
  9. const getCli = memoize(() => require("../cli"));
  10. const ESC = "\u001B[";
  11. const CURSOR_UP = `${ESC}1A`;
  12. const CLEAR_LINE = `${ESC}2K\r`;
  13. /** @import Compiler from "../Compiler" */
  14. /**
  15. * @import {
  16. * InfrastructureLoggingNormalizedWithDefaults
  17. * } from "../config/defaults"
  18. */
  19. /** @import { LoggerConsole } from "../logging/createConsoleLogger" */
  20. /**
  21. * @typedef {object} StatusMessageState
  22. * @property {string[] | undefined} currentMessage current status message
  23. * @property {number} currentLines current status message rows
  24. */
  25. /** @type {WeakMap<Compiler, StatusMessageState>} */
  26. const logStatusStateByCompiler = new WeakMap();
  27. /** @type {Set<StatusMessageState>} */
  28. const logStatusStates = new Set();
  29. /**
  30. * Returns status state
  31. * @param {Compiler} compiler compiler
  32. * @returns {StatusMessageState} status state
  33. */
  34. const getLogStatusState = (compiler) => {
  35. let state = logStatusStateByCompiler.get(compiler);
  36. if (state === undefined) {
  37. state = {
  38. currentMessage: undefined,
  39. currentLines: 0
  40. };
  41. logStatusStateByCompiler.set(compiler, state);
  42. logStatusStates.add(state);
  43. }
  44. return state;
  45. };
  46. /* eslint-disable no-console */
  47. /**
  48. * Returns logger function.
  49. * @param {object} options options
  50. * @param {boolean=} options.colors colors
  51. * @param {boolean=} options.appendOnly append only
  52. * @param {InfrastructureLoggingNormalizedWithDefaults["stream"]} options.stream stream
  53. * @param {Compiler} options.compiler compiler
  54. * @returns {LoggerConsole} logger function
  55. */
  56. module.exports = ({ colors, appendOnly, stream, compiler }) => {
  57. const c = getCli().createColors({ useColor: Boolean(colors) });
  58. const logStatusState = getLogStatusState(compiler);
  59. let currentIndent = "";
  60. let currentCollapsed = 0;
  61. /**
  62. * Returns indented string.
  63. * @param {string} str string
  64. * @param {string} prefix prefix
  65. * @param {(line: string) => string} colorFn color function
  66. * @returns {string} indented string
  67. */
  68. const indent = (str, prefix, colorFn) => {
  69. if (str === "") return str;
  70. prefix = currentIndent + prefix;
  71. return (
  72. prefix +
  73. str
  74. .split("\n")
  75. .map((line) => colorFn(line))
  76. .join(`\n${prefix}`)
  77. );
  78. };
  79. const clearStatusMessage = () => {
  80. let lines = 0;
  81. for (const state of logStatusStates) {
  82. if (state.currentLines) {
  83. lines += state.currentLines;
  84. state.currentLines = 0;
  85. }
  86. }
  87. for (let i = 0; i < lines; i++) {
  88. if (i > 0) stream.write(CURSOR_UP);
  89. stream.write(CLEAR_LINE);
  90. }
  91. };
  92. const writeStatusMessage = () => {
  93. const column = stream.columns || 40;
  94. /** @type {string[]} */
  95. const all = [];
  96. for (const state of logStatusStates) {
  97. if (!state.currentMessage) continue;
  98. /** @type {string[][]} */
  99. const lines = [[]];
  100. for (const item of state.currentMessage) {
  101. const parts = item.split("\n");
  102. lines[lines.length - 1].push(parts[0]);
  103. for (let i = 1; i < parts.length; i++) {
  104. lines.push([parts[i]]);
  105. }
  106. }
  107. const truncateLines = lines.map((args) =>
  108. truncateArgs(args, column - 1).join(" ")
  109. );
  110. state.currentLines = truncateLines.length;
  111. for (const line of truncateLines) all.push(line);
  112. }
  113. if (all.length === 0) return;
  114. const coloredLines = all.map((str) => c.bold(str));
  115. stream.write(`${CLEAR_LINE}${coloredLines.join(`\n${CLEAR_LINE}`)}`);
  116. };
  117. /**
  118. * @param {EXPECTED_ANY[]} statusMessage status message
  119. * @returns {void}
  120. */
  121. const setStatusMessage = (statusMessage) => {
  122. clearStatusMessage();
  123. logStatusState.currentMessage = statusMessage.map((item) => `${item}`);
  124. writeStatusMessage();
  125. };
  126. /**
  127. * Returns function to write with colors.
  128. * @template T
  129. * @param {string} prefix prefix
  130. * @param {(line: string) => string} colorFn color function
  131. * @returns {(...args: T[]) => void} function to write with colors
  132. */
  133. const writeColored =
  134. (prefix, colorFn) =>
  135. (...args) => {
  136. if (currentCollapsed > 0) return;
  137. clearStatusMessage();
  138. const str = indent(util.format(...args), prefix, colorFn);
  139. stream.write(`${str}\n`);
  140. writeStatusMessage();
  141. };
  142. /** @type {<T extends unknown[]>(...args: T) => void} */
  143. const writeGroupMessage = writeColored("<-> ", (str) => c.bold(c.cyan(str)));
  144. /** @type {<T extends unknown[]>(...args: T) => void} */
  145. const writeGroupCollapsedMessage = writeColored("<+> ", (str) =>
  146. c.bold(c.cyan(str))
  147. );
  148. return {
  149. /** @type {LoggerConsole["log"]} */
  150. log: writeColored(" ", c.bold),
  151. /** @type {LoggerConsole["debug"]} */
  152. debug: writeColored(" ", String),
  153. /** @type {LoggerConsole["trace"]} */
  154. trace: writeColored(" ", String),
  155. /** @type {LoggerConsole["info"]} */
  156. info: writeColored("<i> ", (str) => c.bold(c.green(str))),
  157. /** @type {LoggerConsole["warn"]} */
  158. warn: writeColored("<w> ", (str) => c.bold(c.yellow(str))),
  159. /** @type {LoggerConsole["error"]} */
  160. error: writeColored("<e> ", (str) => c.bold(c.red(str))),
  161. /** @type {LoggerConsole["logTime"]} */
  162. logTime: writeColored("<t> ", (str) => c.bold(c.magenta(str))),
  163. /** @type {LoggerConsole["group"]} */
  164. group: (...args) => {
  165. writeGroupMessage(...args);
  166. if (currentCollapsed > 0) {
  167. currentCollapsed++;
  168. } else {
  169. currentIndent += " ";
  170. }
  171. },
  172. /** @type {LoggerConsole["groupCollapsed"]} */
  173. groupCollapsed: (...args) => {
  174. writeGroupCollapsedMessage(...args);
  175. currentCollapsed++;
  176. },
  177. /** @type {LoggerConsole["groupEnd"]} */
  178. groupEnd: () => {
  179. if (currentCollapsed > 0) {
  180. currentCollapsed--;
  181. } else if (currentIndent.length >= 2) {
  182. currentIndent = currentIndent.slice(0, -2);
  183. }
  184. },
  185. /** @type {LoggerConsole["profile"]} */
  186. profile: console.profile && ((name) => console.profile(name)),
  187. /** @type {LoggerConsole["profileEnd"]} */
  188. profileEnd: console.profileEnd && ((name) => console.profileEnd(name)),
  189. /** @type {LoggerConsole["clear"]} */
  190. clear:
  191. /** @type {() => void} */
  192. (
  193. !appendOnly &&
  194. console.clear &&
  195. (() => {
  196. clearStatusMessage();
  197. console.clear();
  198. writeStatusMessage();
  199. })
  200. ),
  201. /** @type {LoggerConsole["status"]} */
  202. status: appendOnly
  203. ? writeColored("<s> ", String)
  204. : (name, ...args) => {
  205. args = args.filter(Boolean);
  206. if (name === undefined && args.length === 0) {
  207. clearStatusMessage();
  208. logStatusState.currentMessage = undefined;
  209. } else if (
  210. typeof name === "string" &&
  211. name.startsWith("[webpack.Progress] ")
  212. ) {
  213. setStatusMessage([name.slice(19), ...args]);
  214. } else if (name === "[webpack.Progress]") {
  215. setStatusMessage([...args]);
  216. } else {
  217. setStatusMessage([name, ...args]);
  218. }
  219. }
  220. };
  221. };