@@ -3,6 +3,44 @@ const npmlog = require('npmlog')
3
3
const log = require ( './log-shim.js' )
4
4
const { explain } = require ( './explain-eresolve.js' )
5
5
6
+ const originalCustomInspect = Symbol ( 'npm.display.original.util.inspect.custom' )
7
+
8
+ // These are most assuredly not a mistake
9
+ // https://eslint.org/docs/latest/rules/no-control-regex
10
+ /* eslint-disable no-control-regex */
11
+ // \x00 through \x1f, \x7f through \x9f, not including \x09 \x0a \x0b \x0d
12
+ const hasC01 = / [ \x00 - \x08 \x0c \x0e - \x1f \x7f - \x9f ] /
13
+ // Allows everything up to '[38;5;255m' in 8 bit notation
14
+ const allowedSGR = / ^ \[ [ 0 - 9 ; ] { 0 , 8 } m /
15
+ // '[38;5;255m'.length
16
+ const sgrMaxLen = 10
17
+
18
+ // Strips all ANSI C0 and C1 control characters (except for SGR up to 8 bit)
19
+ function stripC01 ( str ) {
20
+ if ( ! hasC01 . test ( str ) ) {
21
+ return str
22
+ }
23
+ let result = ''
24
+ for ( let i = 0 ; i < str . length ; i ++ ) {
25
+ const char = str [ i ]
26
+ const code = char . charCodeAt ( 0 )
27
+ if ( ! hasC01 . test ( char ) ) {
28
+ // Most characters are in this set so continue early if we can
29
+ result = `${ result } ${ char } `
30
+ } else if ( code === 27 && allowedSGR . test ( str . slice ( i + 1 , i + sgrMaxLen + 1 ) ) ) {
31
+ // \x1b with allowed SGR
32
+ result = `${ result } \x1b`
33
+ } else if ( code <= 31 ) {
34
+ // escape all other C0 control characters besides \x7f
35
+ result = `${ result } ^${ String . fromCharCode ( code + 64 ) } `
36
+ } else {
37
+ // hasC01 ensures this is now a C1 control character or \x7f
38
+ result = `${ result } ^${ String . fromCharCode ( code - 64 ) } `
39
+ }
40
+ }
41
+ return result
42
+ }
43
+
6
44
class Display {
7
45
#chalk = null
8
46
@@ -12,6 +50,57 @@ class Display {
12
50
log . pause ( )
13
51
}
14
52
53
+ static clean ( output ) {
54
+ if ( typeof output === 'string' ) {
55
+ // Strings are cleaned inline
56
+ return stripC01 ( output )
57
+ }
58
+ if ( ! output || typeof output !== 'object' ) {
59
+ // Numbers, booleans, null all end up here and don't need cleaning
60
+ return output
61
+ }
62
+ // output && typeof output === 'object'
63
+ // We can't use hasOwn et al for detecting the original but we can use it
64
+ // for detecting the properties we set via defineProperty
65
+ if (
66
+ output [ inspect . custom ] &&
67
+ ( ! Object . hasOwn ( output , originalCustomInspect ) )
68
+ ) {
69
+ // Save the old one if we didn't already do it.
70
+ Object . defineProperty ( output , originalCustomInspect , {
71
+ value : output [ inspect . custom ] ,
72
+ writable : true ,
73
+ } )
74
+ }
75
+ if ( ! Object . hasOwn ( output , originalCustomInspect ) ) {
76
+ // Put a dummy one in for when we run multiple times on the same object
77
+ Object . defineProperty ( output , originalCustomInspect , {
78
+ value : function ( ) {
79
+ return this
80
+ } ,
81
+ writable : true ,
82
+ } )
83
+ }
84
+ // Set the custom inspect to our own function
85
+ Object . defineProperty ( output , inspect . custom , {
86
+ value : function ( ) {
87
+ const toClean = this [ originalCustomInspect ] ( )
88
+ // Custom inspect can return things other than objects, check type again
89
+ if ( typeof toClean === 'string' ) {
90
+ // Strings are cleaned inline
91
+ return stripC01 ( toClean )
92
+ }
93
+ if ( ! toClean || typeof toClean !== 'object' ) {
94
+ // Numbers, booleans, null all end up here and don't need cleaning
95
+ return toClean
96
+ }
97
+ return stripC01 ( inspect ( toClean , { customInspect : false } ) )
98
+ } ,
99
+ writable : true ,
100
+ } )
101
+ return output
102
+ }
103
+
15
104
on ( ) {
16
105
process . on ( 'log' , this . #logHandler)
17
106
}
@@ -103,7 +192,7 @@ class Display {
103
192
// Explicitly call these on npmlog and not log shim
104
193
// This is the final place we should call npmlog before removing it.
105
194
#npmlog ( level , ...args ) {
106
- npmlog [ level ] ( ...args )
195
+ npmlog [ level ] ( ...args . map ( Display . clean ) )
107
196
}
108
197
109
198
// Also (and this is a really inexcusable kludge), we patch the
@@ -112,8 +201,8 @@ class Display {
112
201
// highly abbreviated explanation of what's being overridden.
113
202
#eresolveWarn ( level , heading , message , expl ) {
114
203
if ( level === 'warn' &&
115
- heading === 'ERESOLVE' &&
116
- expl && typeof expl === 'object'
204
+ heading === 'ERESOLVE' &&
205
+ expl && typeof expl === 'object'
117
206
) {
118
207
this . #npmlog( level , heading , message )
119
208
this . #npmlog( level , '' , explain ( expl , this . #chalk, 2 ) )
0 commit comments