11cb0ef41Sopenharmony_ciconst { inspect } = require('util') 21cb0ef41Sopenharmony_ciconst npmlog = require('npmlog') 31cb0ef41Sopenharmony_ciconst log = require('./log-shim.js') 41cb0ef41Sopenharmony_ciconst { explain } = require('./explain-eresolve.js') 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ciconst originalCustomInspect = Symbol('npm.display.original.util.inspect.custom') 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ci// These are most assuredly not a mistake 91cb0ef41Sopenharmony_ci// https://eslint.org/docs/latest/rules/no-control-regex 101cb0ef41Sopenharmony_ci/* eslint-disable no-control-regex */ 111cb0ef41Sopenharmony_ci// \x00 through \x1f, \x7f through \x9f, not including \x09 \x0a \x0b \x0d 121cb0ef41Sopenharmony_ciconst hasC01 = /[\x00-\x08\x0c\x0e-\x1f\x7f-\x9f]/ 131cb0ef41Sopenharmony_ci// Allows everything up to '[38;5;255m' in 8 bit notation 141cb0ef41Sopenharmony_ciconst allowedSGR = /^\[[0-9;]{0,8}m/ 151cb0ef41Sopenharmony_ci// '[38;5;255m'.length 161cb0ef41Sopenharmony_ciconst sgrMaxLen = 10 171cb0ef41Sopenharmony_ci 181cb0ef41Sopenharmony_ci// Strips all ANSI C0 and C1 control characters (except for SGR up to 8 bit) 191cb0ef41Sopenharmony_cifunction stripC01 (str) { 201cb0ef41Sopenharmony_ci if (!hasC01.test(str)) { 211cb0ef41Sopenharmony_ci return str 221cb0ef41Sopenharmony_ci } 231cb0ef41Sopenharmony_ci let result = '' 241cb0ef41Sopenharmony_ci for (let i = 0; i < str.length; i++) { 251cb0ef41Sopenharmony_ci const char = str[i] 261cb0ef41Sopenharmony_ci const code = char.charCodeAt(0) 271cb0ef41Sopenharmony_ci if (!hasC01.test(char)) { 281cb0ef41Sopenharmony_ci // Most characters are in this set so continue early if we can 291cb0ef41Sopenharmony_ci result = `${result}${char}` 301cb0ef41Sopenharmony_ci } else if (code === 27 && allowedSGR.test(str.slice(i + 1, i + sgrMaxLen + 1))) { 311cb0ef41Sopenharmony_ci // \x1b with allowed SGR 321cb0ef41Sopenharmony_ci result = `${result}\x1b` 331cb0ef41Sopenharmony_ci } else if (code <= 31) { 341cb0ef41Sopenharmony_ci // escape all other C0 control characters besides \x7f 351cb0ef41Sopenharmony_ci result = `${result}^${String.fromCharCode(code + 64)}` 361cb0ef41Sopenharmony_ci } else { 371cb0ef41Sopenharmony_ci // hasC01 ensures this is now a C1 control character or \x7f 381cb0ef41Sopenharmony_ci result = `${result}^${String.fromCharCode(code - 64)}` 391cb0ef41Sopenharmony_ci } 401cb0ef41Sopenharmony_ci } 411cb0ef41Sopenharmony_ci return result 421cb0ef41Sopenharmony_ci} 431cb0ef41Sopenharmony_ci 441cb0ef41Sopenharmony_ciclass Display { 451cb0ef41Sopenharmony_ci #chalk = null 461cb0ef41Sopenharmony_ci 471cb0ef41Sopenharmony_ci constructor () { 481cb0ef41Sopenharmony_ci // pause by default until config is loaded 491cb0ef41Sopenharmony_ci this.on() 501cb0ef41Sopenharmony_ci log.pause() 511cb0ef41Sopenharmony_ci } 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci static clean (output) { 541cb0ef41Sopenharmony_ci if (typeof output === 'string') { 551cb0ef41Sopenharmony_ci // Strings are cleaned inline 561cb0ef41Sopenharmony_ci return stripC01(output) 571cb0ef41Sopenharmony_ci } 581cb0ef41Sopenharmony_ci if (!output || typeof output !== 'object') { 591cb0ef41Sopenharmony_ci // Numbers, booleans, null all end up here and don't need cleaning 601cb0ef41Sopenharmony_ci return output 611cb0ef41Sopenharmony_ci } 621cb0ef41Sopenharmony_ci // output && typeof output === 'object' 631cb0ef41Sopenharmony_ci // We can't use hasOwn et al for detecting the original but we can use it 641cb0ef41Sopenharmony_ci // for detecting the properties we set via defineProperty 651cb0ef41Sopenharmony_ci if ( 661cb0ef41Sopenharmony_ci output[inspect.custom] && 671cb0ef41Sopenharmony_ci (!Object.hasOwn(output, originalCustomInspect)) 681cb0ef41Sopenharmony_ci ) { 691cb0ef41Sopenharmony_ci // Save the old one if we didn't already do it. 701cb0ef41Sopenharmony_ci Object.defineProperty(output, originalCustomInspect, { 711cb0ef41Sopenharmony_ci value: output[inspect.custom], 721cb0ef41Sopenharmony_ci writable: true, 731cb0ef41Sopenharmony_ci }) 741cb0ef41Sopenharmony_ci } 751cb0ef41Sopenharmony_ci if (!Object.hasOwn(output, originalCustomInspect)) { 761cb0ef41Sopenharmony_ci // Put a dummy one in for when we run multiple times on the same object 771cb0ef41Sopenharmony_ci Object.defineProperty(output, originalCustomInspect, { 781cb0ef41Sopenharmony_ci value: function () { 791cb0ef41Sopenharmony_ci return this 801cb0ef41Sopenharmony_ci }, 811cb0ef41Sopenharmony_ci writable: true, 821cb0ef41Sopenharmony_ci }) 831cb0ef41Sopenharmony_ci } 841cb0ef41Sopenharmony_ci // Set the custom inspect to our own function 851cb0ef41Sopenharmony_ci Object.defineProperty(output, inspect.custom, { 861cb0ef41Sopenharmony_ci value: function () { 871cb0ef41Sopenharmony_ci const toClean = this[originalCustomInspect]() 881cb0ef41Sopenharmony_ci // Custom inspect can return things other than objects, check type again 891cb0ef41Sopenharmony_ci if (typeof toClean === 'string') { 901cb0ef41Sopenharmony_ci // Strings are cleaned inline 911cb0ef41Sopenharmony_ci return stripC01(toClean) 921cb0ef41Sopenharmony_ci } 931cb0ef41Sopenharmony_ci if (!toClean || typeof toClean !== 'object') { 941cb0ef41Sopenharmony_ci // Numbers, booleans, null all end up here and don't need cleaning 951cb0ef41Sopenharmony_ci return toClean 961cb0ef41Sopenharmony_ci } 971cb0ef41Sopenharmony_ci return stripC01(inspect(toClean, { customInspect: false })) 981cb0ef41Sopenharmony_ci }, 991cb0ef41Sopenharmony_ci writable: true, 1001cb0ef41Sopenharmony_ci }) 1011cb0ef41Sopenharmony_ci return output 1021cb0ef41Sopenharmony_ci } 1031cb0ef41Sopenharmony_ci 1041cb0ef41Sopenharmony_ci on () { 1051cb0ef41Sopenharmony_ci process.on('log', this.#logHandler) 1061cb0ef41Sopenharmony_ci } 1071cb0ef41Sopenharmony_ci 1081cb0ef41Sopenharmony_ci off () { 1091cb0ef41Sopenharmony_ci process.off('log', this.#logHandler) 1101cb0ef41Sopenharmony_ci // Unbalanced calls to enable/disable progress 1111cb0ef41Sopenharmony_ci // will leave change listeners on the tracker 1121cb0ef41Sopenharmony_ci // This pretty much only happens in tests but 1131cb0ef41Sopenharmony_ci // this removes the event emitter listener warnings 1141cb0ef41Sopenharmony_ci log.tracker.removeAllListeners() 1151cb0ef41Sopenharmony_ci } 1161cb0ef41Sopenharmony_ci 1171cb0ef41Sopenharmony_ci load (config) { 1181cb0ef41Sopenharmony_ci const { 1191cb0ef41Sopenharmony_ci color, 1201cb0ef41Sopenharmony_ci chalk, 1211cb0ef41Sopenharmony_ci timing, 1221cb0ef41Sopenharmony_ci loglevel, 1231cb0ef41Sopenharmony_ci unicode, 1241cb0ef41Sopenharmony_ci progress, 1251cb0ef41Sopenharmony_ci silent, 1261cb0ef41Sopenharmony_ci heading = 'npm', 1271cb0ef41Sopenharmony_ci } = config 1281cb0ef41Sopenharmony_ci 1291cb0ef41Sopenharmony_ci this.#chalk = chalk 1301cb0ef41Sopenharmony_ci 1311cb0ef41Sopenharmony_ci // npmlog is still going away someday, so this is a hack to dynamically 1321cb0ef41Sopenharmony_ci // set the loglevel of timing based on the timing flag, instead of making 1331cb0ef41Sopenharmony_ci // a breaking change to npmlog. The result is that timing logs are never 1341cb0ef41Sopenharmony_ci // shown except when the --timing flag is set. We also need to change 1351cb0ef41Sopenharmony_ci // the index of the silly level since otherwise it is set to -Infinity 1361cb0ef41Sopenharmony_ci // and we can't go any lower than that. silent is still set to Infinify 1371cb0ef41Sopenharmony_ci // because we DO want silent to hide timing levels. This allows for the 1381cb0ef41Sopenharmony_ci // special case of getting timing information while hiding all CLI output 1391cb0ef41Sopenharmony_ci // in order to get perf information that might be affected by writing to 1401cb0ef41Sopenharmony_ci // a terminal. XXX(npmlog): this will be removed along with npmlog 1411cb0ef41Sopenharmony_ci log.levels.silly = -10000 1421cb0ef41Sopenharmony_ci log.levels.timing = log.levels[loglevel] + (timing ? 1 : -1) 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci log.level = loglevel 1451cb0ef41Sopenharmony_ci log.heading = heading 1461cb0ef41Sopenharmony_ci 1471cb0ef41Sopenharmony_ci if (color) { 1481cb0ef41Sopenharmony_ci log.enableColor() 1491cb0ef41Sopenharmony_ci } else { 1501cb0ef41Sopenharmony_ci log.disableColor() 1511cb0ef41Sopenharmony_ci } 1521cb0ef41Sopenharmony_ci 1531cb0ef41Sopenharmony_ci if (unicode) { 1541cb0ef41Sopenharmony_ci log.enableUnicode() 1551cb0ef41Sopenharmony_ci } else { 1561cb0ef41Sopenharmony_ci log.disableUnicode() 1571cb0ef41Sopenharmony_ci } 1581cb0ef41Sopenharmony_ci 1591cb0ef41Sopenharmony_ci // if it's silent, don't show progress 1601cb0ef41Sopenharmony_ci if (progress && !silent) { 1611cb0ef41Sopenharmony_ci log.enableProgress() 1621cb0ef41Sopenharmony_ci } else { 1631cb0ef41Sopenharmony_ci log.disableProgress() 1641cb0ef41Sopenharmony_ci } 1651cb0ef41Sopenharmony_ci 1661cb0ef41Sopenharmony_ci // Resume displaying logs now that we have config 1671cb0ef41Sopenharmony_ci log.resume() 1681cb0ef41Sopenharmony_ci } 1691cb0ef41Sopenharmony_ci 1701cb0ef41Sopenharmony_ci log (...args) { 1711cb0ef41Sopenharmony_ci this.#logHandler(...args) 1721cb0ef41Sopenharmony_ci } 1731cb0ef41Sopenharmony_ci 1741cb0ef41Sopenharmony_ci #logHandler = (level, ...args) => { 1751cb0ef41Sopenharmony_ci try { 1761cb0ef41Sopenharmony_ci this.#log(level, ...args) 1771cb0ef41Sopenharmony_ci } catch (ex) { 1781cb0ef41Sopenharmony_ci try { 1791cb0ef41Sopenharmony_ci // if it crashed once, it might again! 1801cb0ef41Sopenharmony_ci this.#npmlog('verbose', `attempt to log ${inspect(args)} crashed`, ex) 1811cb0ef41Sopenharmony_ci } catch (ex2) { 1821cb0ef41Sopenharmony_ci // eslint-disable-next-line no-console 1831cb0ef41Sopenharmony_ci console.error(`attempt to log ${inspect(args)} crashed`, ex, ex2) 1841cb0ef41Sopenharmony_ci } 1851cb0ef41Sopenharmony_ci } 1861cb0ef41Sopenharmony_ci } 1871cb0ef41Sopenharmony_ci 1881cb0ef41Sopenharmony_ci #log (...args) { 1891cb0ef41Sopenharmony_ci return this.#eresolveWarn(...args) || this.#npmlog(...args) 1901cb0ef41Sopenharmony_ci } 1911cb0ef41Sopenharmony_ci 1921cb0ef41Sopenharmony_ci // Explicitly call these on npmlog and not log shim 1931cb0ef41Sopenharmony_ci // This is the final place we should call npmlog before removing it. 1941cb0ef41Sopenharmony_ci #npmlog (level, ...args) { 1951cb0ef41Sopenharmony_ci npmlog[level](...args.map(Display.clean)) 1961cb0ef41Sopenharmony_ci } 1971cb0ef41Sopenharmony_ci 1981cb0ef41Sopenharmony_ci // Also (and this is a really inexcusable kludge), we patch the 1991cb0ef41Sopenharmony_ci // log.warn() method so that when we see a peerDep override 2001cb0ef41Sopenharmony_ci // explanation from Arborist, we can replace the object with a 2011cb0ef41Sopenharmony_ci // highly abbreviated explanation of what's being overridden. 2021cb0ef41Sopenharmony_ci #eresolveWarn (level, heading, message, expl) { 2031cb0ef41Sopenharmony_ci if (level === 'warn' && 2041cb0ef41Sopenharmony_ci heading === 'ERESOLVE' && 2051cb0ef41Sopenharmony_ci expl && typeof expl === 'object' 2061cb0ef41Sopenharmony_ci ) { 2071cb0ef41Sopenharmony_ci this.#npmlog(level, heading, message) 2081cb0ef41Sopenharmony_ci this.#npmlog(level, '', explain(expl, this.#chalk, 2)) 2091cb0ef41Sopenharmony_ci // Return true to short circuit other log in chain 2101cb0ef41Sopenharmony_ci return true 2111cb0ef41Sopenharmony_ci } 2121cb0ef41Sopenharmony_ci } 2131cb0ef41Sopenharmony_ci} 2141cb0ef41Sopenharmony_ci 2151cb0ef41Sopenharmony_cimodule.exports = Display 216