1
- const { stripVTControlCharacters } = require ( 'node:util' )
1
+ /* eslint-disable max-len */
2
+ const { stripVTControlCharacters : strip } = require ( 'node:util' )
2
3
const { Minipass } = require ( 'minipass' )
3
- const columnify = require ( 'columnify' )
4
4
5
5
// This module consumes package data in the following format:
6
6
//
@@ -16,14 +16,48 @@ const columnify = require('columnify')
16
16
// The returned stream will format this package data
17
17
// into a byte stream of formatted, displayable output.
18
18
19
- module . exports = async ( opts ) => {
20
- return opts . json ? new JSONOutputStream ( ) : new TextOutputStream ( opts )
19
+ function filter ( data , exclude ) {
20
+ const words = [ data . name ]
21
+ . concat ( data . maintainers . map ( m => m . username ) )
22
+ . concat ( data . keywords || [ ] )
23
+ . map ( f => f ?. trim ?. ( ) )
24
+ . filter ( Boolean )
25
+ . join ( ' ' )
26
+ . toLowerCase ( )
27
+
28
+ if ( exclude . find ( pattern => {
29
+ // Treats both /foo and /foo/ as regex searches
30
+ if ( pattern . startsWith ( '/' ) ) {
31
+ if ( pattern . endsWith ( '/' ) ) {
32
+ pattern = pattern . slice ( 0 , - 1 )
33
+ }
34
+ return words . match ( new RegExp ( pattern . slice ( 1 ) ) )
35
+ }
36
+ return words . includes ( pattern )
37
+ } ) ) {
38
+ return false
39
+ }
40
+
41
+ return true
42
+ }
43
+
44
+ module . exports = ( opts ) => {
45
+ return opts . json ? new JSONOutputStream ( opts ) : new TextOutputStream ( opts )
21
46
}
22
47
23
48
class JSONOutputStream extends Minipass {
24
49
#didFirst = false
50
+ #exclude
51
+
52
+ constructor ( opts ) {
53
+ super ( )
54
+ this . #exclude = opts . exclude
55
+ }
25
56
26
57
write ( obj ) {
58
+ if ( ! filter ( obj , this . #exclude) ) {
59
+ return
60
+ }
27
61
if ( ! this . #didFirst) {
28
62
super . write ( '[\n' )
29
63
this . #didFirst = true
@@ -41,94 +75,96 @@ class JSONOutputStream extends Minipass {
41
75
}
42
76
43
77
class TextOutputStream extends Minipass {
44
- #opts
45
- #line = 0
78
+ #args
79
+ #chalk
80
+ #exclude
81
+ #parseable
46
82
47
83
constructor ( opts ) {
48
84
super ( )
49
- this . #opts = opts
85
+ this . #args = opts . args . map ( s => s . toLowerCase ( ) ) . filter ( Boolean )
86
+ this . #chalk = opts . npm . chalk
87
+ this . #exclude = opts . exclude
88
+ this . #parseable = opts . parseable
50
89
}
51
90
52
- write ( pkg ) {
53
- return super . write ( this . #prettify ( pkg ) )
54
- }
55
-
56
- #prettify ( data ) {
91
+ write ( data ) {
92
+ if ( ! filter ( data , this . #exclude ) ) {
93
+ return
94
+ }
95
+ // Normalize
57
96
const pkg = {
58
- author : data . maintainers . map ( ( m ) => `=${ stripVTControlCharacters ( m . username ) } ` ) . join ( ' ' ) ,
59
- date : 'prehistoric' ,
60
- description : stripVTControlCharacters ( data . description ?? '' ) ,
61
- keywords : '' ,
62
- name : stripVTControlCharacters ( data . name ) ,
97
+ authors : data . maintainers . map ( ( m ) => `${ strip ( m . username ) } ` ) . join ( ' ' ) ,
98
+ publisher : strip ( data . publisher . username ) ,
99
+ date : data . date ? data . date . toISOString ( ) . slice ( 0 , 10 ) : 'prehistoric' ,
100
+ description : strip ( data . description ?? '' ) ,
101
+ keywords : [ ] ,
102
+ name : strip ( data . name ) ,
63
103
version : data . version ,
64
104
}
65
105
if ( Array . isArray ( data . keywords ) ) {
66
- pkg . keywords = data . keywords . map ( ( k ) => stripVTControlCharacters ( k ) ) . join ( ' ' )
106
+ pkg . keywords = data . keywords . map ( strip )
67
107
} else if ( typeof data . keywords === 'string' ) {
68
- pkg . keywords = stripVTControlCharacters ( data . keywords . replace ( / [ , \s ] + / , ' ' ) )
69
- }
70
- if ( data . date ) {
71
- pkg . date = data . date . toISOString ( ) . split ( 'T' ) [ 0 ] // remove time
108
+ pkg . keywords = strip ( data . keywords . replace ( / [ , \s ] + / , ' ' ) ) . split ( ' ' )
72
109
}
73
110
74
- const columns = [ 'name' , 'description' , 'author' , 'date' , 'version' , 'keywords' ]
75
- if ( this . #opts. parseable ) {
76
- return columns . map ( ( col ) => pkg [ col ] && ( '' + pkg [ col ] ) . replace ( / \t / g, ' ' ) ) . join ( '\t' )
111
+ let output
112
+ if ( this . #parseable) {
113
+ output = [ pkg . name , pkg . description , pkg . author , pkg . date , pkg . version , pkg . keywords ]
114
+ . filter ( Boolean )
115
+ . map ( col => ( '' + col ) . replace ( / \t / g, ' ' ) ) . join ( '\t' )
116
+ return super . write ( output )
77
117
}
78
118
79
- // stdout in tap is never a tty
80
- /* istanbul ignore next */
81
- const maxWidth = process . stdout . isTTY ? process . stdout . getWindowSize ( ) [ 0 ] : Infinity
82
- let output = columnify (
83
- [ pkg ] ,
84
- {
85
- include : columns ,
86
- showHeaders : ++ this . #line <= 1 ,
87
- columnSplitter : ' | ' ,
88
- truncate : ! this . #opts. long ,
89
- config : {
90
- name : { minWidth : 25 , maxWidth : 25 , truncate : false , truncateMarker : '' } ,
91
- description : { minWidth : 20 , maxWidth : 20 } ,
92
- author : { minWidth : 15 , maxWidth : 15 } ,
93
- date : { maxWidth : 11 } ,
94
- version : { minWidth : 8 , maxWidth : 8 } ,
95
- keywords : { maxWidth : Infinity } ,
96
- } ,
119
+ const keywords = pkg . keywords . map ( k => {
120
+ if ( this . #args. includes ( k ) ) {
121
+ return this . #chalk. cyan ( k )
122
+ } else {
123
+ return k
124
+ }
125
+ } ) . join ( ' ' )
126
+
127
+ let description = [ ]
128
+ for ( const arg of this . #args) {
129
+ const finder = pkg . description . toLowerCase ( ) . split ( arg . toLowerCase ( ) )
130
+ let p = 0
131
+ for ( const f of finder ) {
132
+ description . push ( pkg . description . slice ( p , p + f . length ) )
133
+ const word = pkg . description . slice ( p + f . length , p + f . length + arg . length )
134
+ description . push ( this . #chalk. cyan ( word ) )
135
+ p += f . length + arg . length
97
136
}
98
- ) . split ( '\n' ) . map ( line => line . slice ( 0 , maxWidth ) ) . join ( '\n' )
99
-
100
- if ( ! this . #opts. color ) {
101
- return output
102
137
}
103
-
104
- const colors = [ '31m' , '33m' , '32m' , '36m' , '34m' , '35m' ]
105
-
106
- this . #opts. args . forEach ( ( arg , i ) => {
107
- const markStart = String . fromCharCode ( i % colors . length + 1 )
108
- const markEnd = String . fromCharCode ( 0 )
109
-
110
- if ( arg . charAt ( 0 ) === '/' ) {
111
- output = output . replace (
112
- new RegExp ( arg . slice ( 1 , - 1 ) , 'gi' ) ,
113
- bit => `${ markStart } ${ bit } ${ markEnd } `
114
- )
115
- } else {
116
- // just a normal string, do the split/map thing
138
+ description = description . filter ( Boolean )
139
+ let name = pkg . name
140
+ if ( this . #args. includes ( pkg . name ) ) {
141
+ name = this . #chalk. cyan ( pkg . name )
142
+ } else {
143
+ name = [ ]
144
+ for ( const arg of this . #args) {
145
+ const finder = pkg . name . toLowerCase ( ) . split ( arg . toLowerCase ( ) )
117
146
let p = 0
118
-
119
- output = output . toLowerCase ( ) . split ( arg . toLowerCase ( ) ) . map ( piece => {
120
- piece = output . slice ( p , p + piece . length )
121
- p += piece . length
122
- const mark = `${ markStart } ${ output . slice ( p , p + arg . length ) } ${ markEnd } `
123
- p += arg . length
124
- return `${ piece } ${ mark } `
125
- } ) . join ( '' )
147
+ for ( const f of finder ) {
148
+ name . push ( pkg . name . slice ( p , p + f . length ) )
149
+ const word = pkg . name . slice ( p + f . length , p + f . length + arg . length )
150
+ name . push ( this . #chalk. cyan ( word ) )
151
+ p += f . length + arg . length
152
+ }
126
153
}
127
- } )
154
+ name = this . #chalk. blue ( name . join ( '' ) )
155
+ }
128
156
129
- for ( let i = 1 ; i <= colors . length ; i ++ ) {
130
- output = output . split ( String . fromCharCode ( i ) ) . join ( `\u001B[${ colors [ i - 1 ] } ` )
157
+ if ( description . length ) {
158
+ output = `${ name } \n${ description . join ( '' ) } \n`
159
+ } else {
160
+ output = `${ name } \n`
161
+ }
162
+ output += `Version ${ this . #chalk. blue ( pkg . version ) } published ${ this . #chalk. blue ( pkg . date ) } by ${ this . #chalk. blue ( pkg . publisher ) } \n`
163
+ output += `Maintainers: ${ pkg . authors } \n`
164
+ if ( keywords ) {
165
+ output += `Keywords: ${ keywords } \n`
131
166
}
132
- return output . split ( '\u0000' ) . join ( '\u001B[0m' ) . trim ( )
167
+ output += `${ this . #chalk. blue ( `https://npm.im/${ pkg . name } ` ) } \n`
168
+ return super . write ( output )
133
169
}
134
170
}
0 commit comments