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