Removed the Requirement to Install Python and NodeJS (Now Bundled with Borealis)
This commit is contained in:
539
Dependencies/NodeJS/node_modules/npm/lib/utils/display.js
generated
vendored
Normal file
539
Dependencies/NodeJS/node_modules/npm/lib/utils/display.js
generated
vendored
Normal file
@ -0,0 +1,539 @@
|
||||
const { log, output, input, META } = require('proc-log')
|
||||
const { explain } = require('./explain-eresolve.js')
|
||||
const { formatWithOptions } = require('./format')
|
||||
|
||||
// This is the general approach to color:
|
||||
// Eventually this will be exposed somewhere we can refer to these by name.
|
||||
// Foreground colors only. Never set the background color.
|
||||
/*
|
||||
* Black # (Don't use)
|
||||
* Red # Danger
|
||||
* Green # Success
|
||||
* Yellow # Warning
|
||||
* Blue # Accent
|
||||
* Magenta # Done
|
||||
* Cyan # Emphasis
|
||||
* White # (Don't use)
|
||||
*/
|
||||
|
||||
// Translates log levels to chalk colors
|
||||
const COLOR_PALETTE = ({ chalk: c }) => ({
|
||||
heading: c.bold,
|
||||
title: c.blueBright,
|
||||
timing: c.magentaBright,
|
||||
// loglevels
|
||||
error: c.red,
|
||||
warn: c.yellow,
|
||||
notice: c.cyanBright,
|
||||
http: c.green,
|
||||
info: c.cyan,
|
||||
verbose: c.blue,
|
||||
silly: c.blue.dim,
|
||||
})
|
||||
|
||||
const LEVEL_OPTIONS = {
|
||||
silent: {
|
||||
index: 0,
|
||||
},
|
||||
error: {
|
||||
index: 1,
|
||||
},
|
||||
warn: {
|
||||
index: 2,
|
||||
},
|
||||
notice: {
|
||||
index: 3,
|
||||
},
|
||||
http: {
|
||||
index: 4,
|
||||
},
|
||||
info: {
|
||||
index: 5,
|
||||
},
|
||||
verbose: {
|
||||
index: 6,
|
||||
},
|
||||
silly: {
|
||||
index: 7,
|
||||
},
|
||||
}
|
||||
|
||||
const LEVEL_METHODS = {
|
||||
...LEVEL_OPTIONS,
|
||||
[log.KEYS.timing]: {
|
||||
show: ({ timing, index }) => !!timing && index !== 0,
|
||||
},
|
||||
}
|
||||
|
||||
const setBlocking = (stream) => {
|
||||
// Copied from https://github.com/yargs/set-blocking
|
||||
// https://raw.githubusercontent.com/yargs/set-blocking/master/LICENSE.txt
|
||||
/* istanbul ignore next - we trust that this works */
|
||||
if (stream._handle && stream.isTTY && typeof stream._handle.setBlocking === 'function') {
|
||||
stream._handle.setBlocking(true)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
// These are important
|
||||
// This is the key that is returned to the user for errors
|
||||
const ERROR_KEY = 'error'
|
||||
// This is the key producers use to indicate that there
|
||||
// is a json error that should be merged into the finished output
|
||||
const JSON_ERROR_KEY = 'jsonError'
|
||||
|
||||
const isPlainObject = (v) => v && typeof v === 'object' && !Array.isArray(v)
|
||||
|
||||
const getArrayOrObject = (items) => {
|
||||
if (items.length) {
|
||||
const foundNonObject = items.find(o => !isPlainObject(o))
|
||||
// Non-objects and arrays cant be merged, so just return the first item
|
||||
if (foundNonObject) {
|
||||
return foundNonObject
|
||||
}
|
||||
// We use objects with 0,1,2,etc keys to merge array
|
||||
if (items.every((o, i) => Object.hasOwn(o, i))) {
|
||||
return Object.assign([], ...items)
|
||||
}
|
||||
}
|
||||
// Otherwise its an object with all object items merged together
|
||||
return Object.assign({}, ...items.filter(o => isPlainObject(o)))
|
||||
}
|
||||
|
||||
const getJsonBuffer = ({ [JSON_ERROR_KEY]: metaError }, buffer) => {
|
||||
const items = []
|
||||
// meta also contains the meta object passed to flush
|
||||
const errors = metaError ? [metaError] : []
|
||||
// index 1 is the meta, 2 is the logged argument
|
||||
for (const [, { [JSON_ERROR_KEY]: error }, obj] of buffer) {
|
||||
if (obj) {
|
||||
items.push(obj)
|
||||
}
|
||||
if (error) {
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (!items.length && !errors.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const res = getArrayOrObject(items)
|
||||
|
||||
// This skips any error checking since we can only set an error property
|
||||
// on an object that can be stringified
|
||||
// XXX(BREAKING_CHANGE): remove this in favor of always returning an object with result and error keys
|
||||
if (isPlainObject(res) && errors.length) {
|
||||
// This is not ideal. JSON output has always been keyed at the root with an `error`
|
||||
// key, so we cant change that without it being a breaking change. At the same time
|
||||
// some commands output arbitrary keys at the top level of the output, such as package
|
||||
// names. So the output could already have the same key. The choice here is to overwrite
|
||||
// it with our error since that is (probably?) more important.
|
||||
// XXX(BREAKING_CHANGE): all json output should be keyed under well known keys, eg `result` and `error`
|
||||
if (res[ERROR_KEY]) {
|
||||
log.warn('', `overwriting existing ${ERROR_KEY} on json output`)
|
||||
}
|
||||
res[ERROR_KEY] = getArrayOrObject(errors)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const withMeta = (handler) => (level, ...args) => {
|
||||
let meta = {}
|
||||
const last = args.at(-1)
|
||||
if (last && typeof last === 'object' && Object.hasOwn(last, META)) {
|
||||
meta = args.pop()
|
||||
}
|
||||
return handler(level, meta, ...args)
|
||||
}
|
||||
|
||||
class Display {
|
||||
#logState = {
|
||||
buffering: true,
|
||||
buffer: [],
|
||||
}
|
||||
|
||||
#outputState = {
|
||||
buffering: true,
|
||||
buffer: [],
|
||||
}
|
||||
|
||||
// colors
|
||||
#noColorChalk
|
||||
#stdoutChalk
|
||||
#stdoutColor
|
||||
#stderrChalk
|
||||
#stderrColor
|
||||
#logColors
|
||||
|
||||
// progress
|
||||
#progress
|
||||
|
||||
// options
|
||||
#command
|
||||
#levelIndex
|
||||
#timing
|
||||
#json
|
||||
#heading
|
||||
#silent
|
||||
|
||||
// display streams
|
||||
#stdout
|
||||
#stderr
|
||||
|
||||
constructor ({ stdout, stderr }) {
|
||||
this.#stdout = setBlocking(stdout)
|
||||
this.#stderr = setBlocking(stderr)
|
||||
|
||||
// Handlers are set immediately so they can buffer all events
|
||||
process.on('log', this.#logHandler)
|
||||
process.on('output', this.#outputHandler)
|
||||
process.on('input', this.#inputHandler)
|
||||
this.#progress = new Progress({ stream: stderr })
|
||||
}
|
||||
|
||||
off () {
|
||||
process.off('log', this.#logHandler)
|
||||
this.#logState.buffer.length = 0
|
||||
process.off('output', this.#outputHandler)
|
||||
this.#outputState.buffer.length = 0
|
||||
process.off('input', this.#inputHandler)
|
||||
this.#progress.off()
|
||||
}
|
||||
|
||||
get chalk () {
|
||||
return {
|
||||
noColor: this.#noColorChalk,
|
||||
stdout: this.#stdoutChalk,
|
||||
stderr: this.#stderrChalk,
|
||||
}
|
||||
}
|
||||
|
||||
async load ({
|
||||
command,
|
||||
heading,
|
||||
json,
|
||||
loglevel,
|
||||
progress,
|
||||
stderrColor,
|
||||
stdoutColor,
|
||||
timing,
|
||||
unicode,
|
||||
}) {
|
||||
// get createSupportsColor from chalk directly if this lands
|
||||
// https://github.com/chalk/chalk/pull/600
|
||||
const [{ Chalk }, { createSupportsColor }] = await Promise.all([
|
||||
import('chalk'),
|
||||
import('supports-color'),
|
||||
])
|
||||
// we get the chalk level based on a null stream meaning chalk will only use
|
||||
// what it knows about the environment to get color support since we already
|
||||
// determined in our definitions that we want to show colors.
|
||||
const level = Math.max(createSupportsColor(null).level, 1)
|
||||
this.#noColorChalk = new Chalk({ level: 0 })
|
||||
this.#stdoutColor = stdoutColor
|
||||
this.#stdoutChalk = stdoutColor ? new Chalk({ level }) : this.#noColorChalk
|
||||
this.#stderrColor = stderrColor
|
||||
this.#stderrChalk = stderrColor ? new Chalk({ level }) : this.#noColorChalk
|
||||
this.#logColors = COLOR_PALETTE({ chalk: this.#stderrChalk })
|
||||
|
||||
this.#command = command
|
||||
this.#levelIndex = LEVEL_OPTIONS[loglevel].index
|
||||
this.#timing = timing
|
||||
this.#json = json
|
||||
this.#heading = heading
|
||||
this.#silent = this.#levelIndex <= 0
|
||||
|
||||
// Emit resume event on the logs which will flush output
|
||||
log.resume()
|
||||
output.flush()
|
||||
this.#progress.load({
|
||||
unicode,
|
||||
enabled: !!progress && !this.#silent,
|
||||
})
|
||||
}
|
||||
|
||||
// STREAM WRITES
|
||||
|
||||
// Write formatted and (non-)colorized output to streams
|
||||
#write (stream, options, ...args) {
|
||||
const colors = stream === this.#stdout ? this.#stdoutColor : this.#stderrColor
|
||||
const value = formatWithOptions({ colors, ...options }, ...args)
|
||||
this.#progress.write(() => stream.write(value))
|
||||
}
|
||||
|
||||
// HANDLERS
|
||||
|
||||
// Arrow function assigned to a private class field so it can be passed
|
||||
// directly as a listener and still reference "this"
|
||||
#logHandler = withMeta((level, meta, ...args) => {
|
||||
switch (level) {
|
||||
case log.KEYS.resume:
|
||||
this.#logState.buffering = false
|
||||
this.#logState.buffer.forEach((item) => this.#tryWriteLog(...item))
|
||||
this.#logState.buffer.length = 0
|
||||
break
|
||||
|
||||
case log.KEYS.pause:
|
||||
this.#logState.buffering = true
|
||||
break
|
||||
|
||||
default:
|
||||
if (this.#logState.buffering) {
|
||||
this.#logState.buffer.push([level, meta, ...args])
|
||||
} else {
|
||||
this.#tryWriteLog(level, meta, ...args)
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
// Arrow function assigned to a private class field so it can be passed
|
||||
// directly as a listener and still reference "this"
|
||||
#outputHandler = withMeta((level, meta, ...args) => {
|
||||
this.#json = typeof meta.json === 'boolean' ? meta.json : this.#json
|
||||
switch (level) {
|
||||
case output.KEYS.flush: {
|
||||
this.#outputState.buffering = false
|
||||
if (this.#json) {
|
||||
const json = getJsonBuffer(meta, this.#outputState.buffer)
|
||||
if (json) {
|
||||
this.#writeOutput(output.KEYS.standard, meta, JSON.stringify(json, null, 2))
|
||||
}
|
||||
} else {
|
||||
this.#outputState.buffer.forEach((item) => this.#writeOutput(...item))
|
||||
}
|
||||
this.#outputState.buffer.length = 0
|
||||
break
|
||||
}
|
||||
|
||||
case output.KEYS.buffer:
|
||||
this.#outputState.buffer.push([output.KEYS.standard, meta, ...args])
|
||||
break
|
||||
|
||||
default:
|
||||
if (this.#outputState.buffering) {
|
||||
this.#outputState.buffer.push([level, meta, ...args])
|
||||
} else {
|
||||
// HACK: Check if the argument looks like a run-script banner. This can be
|
||||
// replaced with proc-log.META in @npmcli/run-script
|
||||
if (typeof args[0] === 'string' && args[0].startsWith('\n> ') && args[0].endsWith('\n')) {
|
||||
if (this.#silent || ['exec', 'explore'].includes(this.#command)) {
|
||||
// Silent mode and some specific commands always hide run script banners
|
||||
break
|
||||
} else if (this.#json) {
|
||||
// In json mode, change output to stderr since we dont want to break json
|
||||
// parsing on stdout if the user is piping to jq or something.
|
||||
// XXX: in a future (breaking?) change it might make sense for run-script to
|
||||
// always output these banners with proc-log.output.error if we think they
|
||||
// align closer with "logging" instead of "output"
|
||||
level = output.KEYS.error
|
||||
}
|
||||
}
|
||||
this.#writeOutput(level, meta, ...args)
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
#inputHandler = withMeta((level, meta, ...args) => {
|
||||
switch (level) {
|
||||
case input.KEYS.start:
|
||||
log.pause()
|
||||
this.#outputState.buffering = true
|
||||
this.#progress.off()
|
||||
break
|
||||
|
||||
case input.KEYS.end:
|
||||
log.resume()
|
||||
output.flush()
|
||||
this.#progress.resume()
|
||||
break
|
||||
|
||||
case input.KEYS.read: {
|
||||
// The convention when calling input.read is to pass in a single fn that returns
|
||||
// the promise to await. resolve and reject are provided by proc-log
|
||||
const [res, rej, p] = args
|
||||
return input.start(() => p()
|
||||
.then(res)
|
||||
.catch(rej)
|
||||
// Any call to procLog.input.read will render a prompt to the user, so we always
|
||||
// add a single newline of output to stdout to move the cursor to the next line
|
||||
.finally(() => output.standard('')))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// OUTPUT
|
||||
|
||||
#writeOutput (level, meta, ...args) {
|
||||
switch (level) {
|
||||
case output.KEYS.standard:
|
||||
this.#write(this.#stdout, {}, ...args)
|
||||
break
|
||||
|
||||
case output.KEYS.error:
|
||||
this.#write(this.#stderr, {}, ...args)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// LOGS
|
||||
|
||||
#tryWriteLog (level, meta, ...args) {
|
||||
try {
|
||||
// Also (and this is a really inexcusable kludge), we patch the
|
||||
// log.warn() method so that when we see a peerDep override
|
||||
// explanation from Arborist, we can replace the object with a
|
||||
// highly abbreviated explanation of what's being overridden.
|
||||
// TODO: this could probably be moved to arborist now that display is refactored
|
||||
const [heading, message, expl] = args
|
||||
if (level === log.KEYS.warn && heading === 'ERESOLVE' && expl && typeof expl === 'object') {
|
||||
this.#writeLog(level, meta, heading, message)
|
||||
this.#writeLog(level, meta, '', explain(expl, this.#stderrChalk, 2))
|
||||
return
|
||||
}
|
||||
this.#writeLog(level, meta, ...args)
|
||||
} catch (ex) {
|
||||
try {
|
||||
// if it crashed once, it might again!
|
||||
this.#writeLog(log.KEYS.verbose, meta, '', `attempt to log crashed`, ...args, ex)
|
||||
} catch (ex2) {
|
||||
// This happens if the object has an inspect method that crashes so just console.error
|
||||
// with the errors but don't do anything else that might error again.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`attempt to log crashed`, ex, ex2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#writeLog (level, meta, ...args) {
|
||||
const levelOpts = LEVEL_METHODS[level]
|
||||
const show = levelOpts.show ?? (({ index }) => levelOpts.index <= index)
|
||||
const force = meta.force && !this.#silent
|
||||
|
||||
if (force || show({ index: this.#levelIndex, timing: this.#timing })) {
|
||||
// this mutates the array so we can pass args directly to format later
|
||||
const title = args.shift()
|
||||
const prefix = [
|
||||
this.#logColors.heading(this.#heading),
|
||||
this.#logColors[level](level),
|
||||
title ? this.#logColors.title(title) : null,
|
||||
]
|
||||
this.#write(this.#stderr, { prefix }, ...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Progress {
|
||||
// Taken from https://github.com/sindresorhus/cli-spinners
|
||||
// MIT License
|
||||
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
static dots = { duration: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] }
|
||||
static lines = { duration: 130, frames: ['-', '\\', '|', '/'] }
|
||||
|
||||
#stream
|
||||
#spinner
|
||||
#enabled = false
|
||||
|
||||
#frameIndex = 0
|
||||
#lastUpdate = 0
|
||||
#interval
|
||||
#timeout
|
||||
|
||||
// We are rendering is enabled option is set and we are not waiting for the render timeout
|
||||
get #rendering () {
|
||||
return this.#enabled && !this.#timeout
|
||||
}
|
||||
|
||||
// We are spinning if enabled option is set and the render interval has been set
|
||||
get #spinning () {
|
||||
return this.#enabled && this.#interval
|
||||
}
|
||||
|
||||
constructor ({ stream }) {
|
||||
this.#stream = stream
|
||||
}
|
||||
|
||||
load ({ enabled, unicode }) {
|
||||
this.#enabled = enabled
|
||||
this.#spinner = unicode ? Progress.dots : Progress.lines
|
||||
// Dont render the spinner for short durations
|
||||
this.#render(200)
|
||||
}
|
||||
|
||||
off () {
|
||||
if (!this.#enabled) {
|
||||
return
|
||||
}
|
||||
clearTimeout(this.#timeout)
|
||||
this.#timeout = null
|
||||
clearInterval(this.#interval)
|
||||
this.#interval = null
|
||||
this.#frameIndex = 0
|
||||
this.#lastUpdate = 0
|
||||
this.#clearSpinner()
|
||||
}
|
||||
|
||||
resume () {
|
||||
this.#render()
|
||||
}
|
||||
|
||||
// If we are currenting rendering the spinner we clear it
|
||||
// before writing our line and then re-render the spinner after.
|
||||
// If not then all we need to do is write the line
|
||||
write (write) {
|
||||
if (this.#spinning) {
|
||||
this.#clearSpinner()
|
||||
}
|
||||
write()
|
||||
if (this.#spinning) {
|
||||
this.#render()
|
||||
}
|
||||
}
|
||||
|
||||
#render (ms) {
|
||||
if (ms) {
|
||||
this.#timeout = setTimeout(() => {
|
||||
this.#timeout = null
|
||||
this.#renderSpinner()
|
||||
}, ms)
|
||||
// Make sure this timeout does not keep the process open
|
||||
this.#timeout.unref()
|
||||
} else {
|
||||
this.#renderSpinner()
|
||||
}
|
||||
}
|
||||
|
||||
#renderSpinner () {
|
||||
if (!this.#rendering) {
|
||||
return
|
||||
}
|
||||
// We always attempt to render immediately but we only request to move to the next
|
||||
// frame if it has been longer than our spinner frame duration since our last update
|
||||
this.#renderFrame(Date.now() - this.#lastUpdate >= this.#spinner.duration)
|
||||
clearInterval(this.#interval)
|
||||
this.#interval = setInterval(() => this.#renderFrame(true), this.#spinner.duration)
|
||||
}
|
||||
|
||||
#renderFrame (next) {
|
||||
if (next) {
|
||||
this.#lastUpdate = Date.now()
|
||||
this.#frameIndex++
|
||||
if (this.#frameIndex >= this.#spinner.frames.length) {
|
||||
this.#frameIndex = 0
|
||||
}
|
||||
}
|
||||
this.#clearSpinner()
|
||||
this.#stream.write(this.#spinner.frames[this.#frameIndex])
|
||||
}
|
||||
|
||||
#clearSpinner () {
|
||||
// Move to the start of the line and clear the rest of the line
|
||||
this.#stream.cursorTo(0)
|
||||
this.#stream.clearLine(1)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Display
|
Reference in New Issue
Block a user