You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
3.9 KiB

// centralized stdout writer.
exports.doColor = doColor
exports.write = write
var npm = require("../npm.js")
, tty = require("tty")
, streams = {}
, ttys = {}
, net = require("net")
, util = require("util")
, deadStreams = {}
function doColor (stream) {
var conf = npm.config.get("color")
return (!conf) ? false
: (conf === "always") ? true
: isatty(stream)
}
function isatty (stream) {
// console.error("isatty?", stream)
if (!tty.isatty) return true
if (!stream) return false
if (stream.isTTY) return true
if (stream && (typeof stream.fd === "number")) {
stream.isTTY = tty.isatty(stream.fd)
}
return stream.isTTY
}
function write (args, stream, lf, cb) {
// console.error("write", [args, stream, lf, cb])
if (typeof cb !== "function" && typeof lf === "function") {
cb = lf
lf = null
}
if (typeof cb !== "function" && typeof stream === "function") {
cb = stream
stream = npm.config.get("outfd")
}
stream = getStream(stream)
// console.error("gotStream", stream)
if (lf == null) lf = isatty(stream)
if (!stream) return cb && cb(), false
if (!Array.isArray(args)) args = [args]
// console.error("write", args)
var msg = ""
, colored = doColor(stream)
msg = args.map(function (arg) {
if (typeof arg !== "string") {
return util.inspect(arg, false, 5, colored) + "\n"
}
if (!colored) arg = arg.replace(/\033\[[0-9;]*m/g, '')
if (!npm.config.get("unicode")) {
arg = arg.replace(/└/g, "`")
.replace(/─/g, "-")
.replace(/├/g, "+")
.replace(/┬/g, "-")
}
return arg
}).join(" ")
// listen to the "output" event to cancel/modify/redirect
npm.output = {stream:stream, message:msg}
npm.emit("output", npm.output)
if (!npm.output) return cb && cb(), false // cancelled
stream = npm.output.stream
msg = npm.output.message
// EPIPE errors just mean that the stream is not listening
// any more. Mark the stream as dead, and return.
if (deadStreams[stream.fd]) {
return cb && cb(), false
}
if (!deadStreams.hasOwnProperty(stream.fd)) {
deadStreams[stream.fd] = false
stream.on("error", function (er) {
if (er.code === "EPIPE") {
deadStreams[stream.fd] = true
return cb && cb()
}
if (stream.listeners("error").length === 1) {
throw er
}
})
}
// use the \r\n in case we're in raw mode.
msg = msg.split(/\r?\n/).concat("").join(lf ? "\r\n" : "\n")
// output to stderr should be synchronous
if (stream === process.stderr || stream.fd === 2) {
process.stderr.write(msg)
if (cb) cb()
return true
}
// console.error("writing ", msg)
var flushed = stream.write(msg)
if (flushed && cb) {
process.nextTick(cb)
} else if (cb) {
stream.once("drain", cb)
}
return flushed
}
var hadError = false
function getStream (fd) {
if (hadError) return
var stream
if (!fd && fd !== 0) return
if (typeof fd === "string") fd = +fd
// console.error("getStream", fd, hadError)
if (fd && typeof fd === "object") {
stream = fd
fd = fd.fd
} else if (streams[fd]) {
stream = streams[fd]
} else {
switch (fd) {
case 1:
stream = process.stdout
stream.fd = fd
stream.writable = true
break
case 2:
stream = process.stderr
stream.fd = fd
stream.writable = true
break
default:
try {
stream = new net.Stream(fd)
if (!stream || !stream.writable) {
throw new Error("Stream not writable")
}
} catch (ex) {
// if this fails, then regular logging is most likely broken.
var er = new Error("cannot output to fd "+fd + ": "+
(ex.stack || ex.message).substr(7) + "\n")
console.error(er.stack)
hadError = true
process.exit(1)
}
}
}
if (!stream || !stream.writable) return
return streams[fd] = stream
}