codekingpro/portable-devtools
115k
1'use strict'
2
3const { log } = require('proc-log')
4const { format } = require('util')
5
6// helper to emit log messages with a predefined prefix
7const withPrefix = (prefix) => log.LEVELS.reduce((acc, level) => {
8 acc[level] = (...args) => log[level](prefix, ...args)
9 return acc
10}, {})
11
12// very basic ansi color generator
13const COLORS = {
14 wrap: (str, colors) => {
15 const codes = colors.filter(c => typeof c === 'number')
16 return `\x1b[${codes.join(';')}m${str}\x1b[0m`
17 },
18 inverse: 7,
19 fg: {
20 black: 30,
21 red: 31,
22 green: 32,
23 yellow: 33,
24 blue: 34,
25 magenta: 35,
26 cyan: 36,
27 white: 37
28 },
29 bg: {
30 black: 40,
31 red: 41,
32 green: 42,
33 yellow: 43,
34 blue: 44,
35 magenta: 45,
36 cyan: 46,
37 white: 47
38 }
39}
40
41class Logger {
42 #buffer = []
43 #paused = null
44 #level = null
45 #stream = null
46
47 // ordered from loudest to quietest
48 #levels = [{
49 id: 'silly',
50 display: 'sill',
51 style: { inverse: true }
52 }, {
53 id: 'verbose',
54 display: 'verb',
55 style: { fg: 'cyan', bg: 'black' }
56 }, {
57 id: 'info',
58 style: { fg: 'green' }
59 }, {
60 id: 'http',
61 style: { fg: 'green', bg: 'black' }
62 }, {
63 id: 'notice',
64 style: { fg: 'cyan', bg: 'black' }
65 }, {
66 id: 'warn',
67 display: 'WARN',
68 style: { fg: 'black', bg: 'yellow' }
69 }, {
70 id: 'error',
71 display: 'ERR!',
72 style: { fg: 'red', bg: 'black' }
73 }]
74
75 constructor (stream) {
76 process.on('log', (...args) => this.#onLog(...args))
77 this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }]))
78 this.level = 'info'
79 this.stream = stream
80 log.pause()
81 }
82
83 get stream () {
84 return this.#stream
85 }
86
87 set stream (stream) {
88 this.#stream = stream
89 }
90
91 get level () {
92 return this.#levels.get(this.#level) ?? null
93 }
94
95 set level (level) {
96 this.#level = this.#levels.get(level)?.id ?? null
97 }
98
99 isVisible (level) {
100 return this.level?.index <= this.#levels.get(level)?.index ?? -1
101 }
102
103 #onLog (...args) {
104 const [level] = args
105
106 if (level === 'pause') {
107 this.#paused = true
108 return
109 }
110
111 if (level === 'resume') {
112 this.#paused = false
113 this.#buffer.forEach((b) => this.#log(...b))
114 this.#buffer.length = 0
115 return
116 }
117
118 if (this.#paused) {
119 this.#buffer.push(args)
120 return
121 }
122
123 this.#log(...args)
124 }
125
126 #color (str, { fg, bg, inverse }) {
127 if (!this.#stream?.isTTY) {
128 return str
129 }
130
131 return COLORS.wrap(str, [
132 COLORS.fg[fg],
133 COLORS.bg[bg],
134 inverse && COLORS.inverse
135 ])
136 }
137
138 #log (levelId, msgPrefix, ...args) {
139 if (!this.isVisible(levelId) || typeof this.#stream?.write !== 'function') {
140 return
141 }
142
143 const level = this.#levels.get(levelId)
144
145 const prefixParts = [
146 this.#color('gyp', { fg: 'white', bg: 'black' }),
147 this.#color(level.display ?? level.id, level.style)
148 ]
149 if (msgPrefix) {
150 prefixParts.push(this.#color(msgPrefix, { fg: 'magenta' }))
151 }
152
153 const prefix = prefixParts.join(' ').trim() + ' '
154 const lines = format(...args).split(/\r?\n/).map(l => prefix + l.trim())
155
156 this.#stream.write(lines.join('\n') + '\n')
157 }
158}
159
160// used to suppress logs in tests
161const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER
162
163module.exports = {
164 logger: new Logger(NULL_LOGGER ? null : process.stderr),
165 stdout: NULL_LOGGER ? () => {} : (...args) => console.log(...args),
166 withPrefix,
167 ...log
168}
169 